| /* |
| |
| Copyright (C) 1993-2012 Hewlett-Packard Company |
| ALL RIGHTS RESERVED. |
| |
| The enclosed software and documentation includes copyrighted works |
| of Hewlett-Packard Co. For as long as you comply with the following |
| limitations, you are hereby authorized to (i) use, reproduce, and |
| modify the software and documentation, and to (ii) distribute the |
| software and documentation, including modifications, for |
| non-commercial purposes only. |
| |
| 1. The enclosed software and documentation is made available at no |
| charge in order to advance the general development of |
| high-performance networking products. |
| |
| 2. You may not delete any copyright notices contained in the |
| software or documentation. All hard copies, and copies in |
| source code or object code form, of the software or |
| documentation (including modifications) must contain at least |
| one of the copyright notices. |
| |
| 3. The enclosed software and documentation has not been subjected |
| to testing and quality control and is not a Hewlett-Packard Co. |
| product. At a future time, Hewlett-Packard Co. may or may not |
| offer a version of the software and documentation as a product. |
| |
| 4. THE SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS". |
| HEWLETT-PACKARD COMPANY DOES NOT WARRANT THAT THE USE, |
| REPRODUCTION, MODIFICATION OR DISTRIBUTION OF THE SOFTWARE OR |
| DOCUMENTATION WILL NOT INFRINGE A THIRD PARTY'S INTELLECTUAL |
| PROPERTY RIGHTS. HP DOES NOT WARRANT THAT THE SOFTWARE OR |
| DOCUMENTATION IS ERROR FREE. HP DISCLAIMS ALL WARRANTIES, |
| EXPRESS AND IMPLIED, WITH REGARD TO THE SOFTWARE AND THE |
| DOCUMENTATION. HP SPECIFICALLY DISCLAIMS ALL WARRANTIES OF |
| MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. |
| |
| 5. HEWLETT-PACKARD COMPANY WILL NOT IN ANY EVENT BE LIABLE FOR ANY |
| DIRECT, INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES |
| (INCLUDING LOST PROFITS) RELATED TO ANY USE, REPRODUCTION, |
| MODIFICATION, OR DISTRIBUTION OF THE SOFTWARE OR DOCUMENTATION. |
| |
| */ |
| |
| #include "netperf_version.h" |
| |
| char netserver_id[]="\ |
| @(#)netserver.c (c) Copyright 1993-2012 Hewlett-Packard Co. Version 2.6.0"; |
| |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #if HAVE_STRING_H |
| # if !STDC_HEADERS && HAVE_MEMORY_H |
| # include <memory.h> |
| # endif |
| # include <string.h> |
| #endif |
| |
| #if HAVE_STRINGS_H |
| # include <strings.h> |
| #endif |
| |
| #if HAVE_LIMITS_H |
| # include <limits.h> |
| #endif |
| |
| #if HAVE_SYS_IPC_H |
| #include <sys/ipc.h> |
| #endif |
| |
| #if HAVE_SYS_IOCTL_H |
| #include <sys/ioctl.h> |
| #endif |
| |
| #if HAVE_SYS_SOCKET_H |
| #include <sys/socket.h> |
| #endif |
| |
| #if HAVE_SYS_STAT_H |
| #include <sys/stat.h> |
| #endif |
| |
| #if HAVE_NETINET_IN_H |
| #include <netinet/in.h> |
| #endif |
| |
| #if HAVE_NETDB_H |
| #include <netdb.h> |
| #endif |
| |
| #if HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| |
| #if HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| |
| #if HAVE_ERRNO_H |
| #include <errno.h> |
| #endif |
| |
| #if HAVE_SIGNAL_H |
| #include <signal.h> |
| /* some OS's have SIGCLD defined as SIGCHLD */ |
| #ifndef SIGCLD |
| #define SIGCLD SIGCHLD |
| #endif /* SIGCLD */ |
| |
| #endif |
| |
| #if !defined(HAVE_SETSID) |
| #if HAVE_SYS_WAIT_H |
| #include <sys/wait.h> |
| #endif |
| #endif |
| |
| #ifdef WIN32 |
| #include <time.h> |
| #include <winsock2.h> |
| |
| #if HAVE_WS2TCPIP_H |
| #include <ws2tcpip.h> |
| #endif |
| |
| #include <windows.h> |
| |
| #include "missing\stdint.h" |
| |
| #define strdup _strdup |
| #define sleep(x) Sleep((x)*1000) |
| #define netperf_socklen_t socklen_t |
| #endif /* WIN32 */ |
| |
| /* unconditional system includes */ |
| |
| #include <sys/types.h> |
| #include <stdio.h> |
| #include <fcntl.h> |
| |
| /* netperf includes */ |
| #include "netlib.h" |
| #include "nettest_bsd.h" |
| |
| #ifdef WANT_UNIX |
| #include "nettest_unix.h" |
| #endif /* WANT_UNIX */ |
| |
| #ifdef WANT_DLPI |
| #include "nettest_dlpi.h" |
| #endif /* WANT_DLPI */ |
| |
| #ifdef WANT_SCTP |
| #include "nettest_sctp.h" |
| #endif |
| |
| #include "netsh.h" |
| |
| #ifndef DEBUG_LOG_FILE_DIR |
| #if defined(WIN32) |
| #define DEBUG_LOG_FILE_DIR "" |
| #elif defined(ANDROID) |
| #define DEBUG_LOG_FILE_DIR "/data/local/tmp/" |
| #else |
| #define DEBUG_LOG_FILE_DIR "/tmp/" |
| #endif |
| #endif /* DEBUG_LOG_FILE_DIR */ |
| |
| #ifndef DEBUG_LOG_FILE |
| #define DEBUG_LOG_FILE DEBUG_LOG_FILE_DIR"netserver.debug" |
| #endif |
| |
| #if !defined(PATH_MAX) |
| #define PATH_MAX MAX_PATH |
| #endif |
| char FileName[PATH_MAX]; |
| |
| char listen_port[10]; |
| |
| struct listen_elt { |
| SOCKET fd; |
| struct listen_elt *next; |
| }; |
| |
| struct listen_elt *listen_list = NULL; |
| |
| SOCKET server_control; |
| |
| int child; /* are we the child of inetd or a parent netserver? |
| */ |
| int netperf_daemon; |
| int daemon_parent = 0; |
| int not_inetd; |
| int want_daemonize; |
| int spawn_on_accept; |
| int suppress_debug = 0; |
| |
| extern char *optarg; |
| extern int optind, opterr; |
| |
| /* char *passphrase = NULL; */ |
| |
| static void |
| init_netserver_globals() { |
| |
| #if defined(__VMS) || defined(VMWARE_UW) |
| spawn_on_accept = 0; |
| want_daemonize = 0; |
| #else |
| spawn_on_accept = 1; |
| #if defined(WIN32) |
| /* we only know how to spawn in WIN32, not daemonize */ |
| want_daemonize = 0; |
| #else |
| want_daemonize = 1; |
| #endif /* WIN32 */ |
| #endif /* __VMS || VMWARE_UW */ |
| |
| child = 0; |
| not_inetd = 0; |
| netperf_daemon = 0; |
| } |
| |
| void |
| unlink_empty_debug_file() { |
| |
| #if !defined(WIN32) |
| struct stat buf; |
| |
| if (stat(FileName,&buf)== 0) { |
| |
| if (buf.st_size == 0) |
| unlink(FileName); |
| } |
| #endif |
| } |
| |
| /* it is important that set_server_sock() be called before this |
| routine as we depend on the control socket being dup()ed out of the |
| way when we go messing about with the streams. */ |
| void |
| open_debug_file() |
| { |
| #if !defined WIN32 |
| #define NETPERF_NULL "/dev/null" |
| #else |
| #define NETPERF_NULL "nul" |
| #endif |
| |
| FILE *rd_null_fp; |
| |
| if (where != NULL) fflush(where); |
| |
| snprintf(FileName, |
| sizeof(FileName), |
| #if defined(WIN32) |
| "%s\\%s_%d", |
| getenv("TEMP"), |
| #else |
| "%s_%d", |
| #endif |
| DEBUG_LOG_FILE, |
| getpid()); |
| if ((where = fopen((suppress_debug) ? NETPERF_NULL : FileName, |
| "w")) == NULL) { |
| perror("netserver: debug file"); |
| exit(1); |
| } |
| |
| #if !defined(WIN32) |
| |
| chmod(FileName,0644); |
| |
| /* redirect stdin to "/dev/null" */ |
| rd_null_fp = fopen(NETPERF_NULL,"r"); |
| if (NULL == rd_null_fp) { |
| fprintf(where, |
| "%s: opening of %s failed: %s (errno %d)\n", |
| __FUNCTION__, |
| NETPERF_NULL, |
| strerror(errno), |
| errno); |
| fflush(where); |
| exit(1); |
| } |
| |
| if (close(STDIN_FILENO) == -1) { |
| fprintf(where, |
| "%s: close of STDIN_FILENO failed: %s (errno %d)\n", |
| __FUNCTION__, |
| strerror(errno), |
| errno); |
| fflush(where); |
| exit(1); |
| } |
| |
| if (dup(fileno(rd_null_fp)) == -1) { |
| fprintf(where, |
| "%s: dup of rd_null_fp to stdin failed: %s (errno %d)\n", |
| __FUNCTION__, |
| strerror(errno), |
| errno); |
| fflush(where); |
| exit(1); |
| } |
| |
| /* redirect stdout to "where" */ |
| if (close(STDOUT_FILENO) == -1) { |
| fprintf(where, |
| "%s: close of STDOUT_FILENO failed: %s (errno %d)\n", |
| __FUNCTION__, |
| strerror(errno), |
| errno); |
| fflush(where); |
| exit(1); |
| } |
| |
| if (dup(fileno(where)) == -1) { |
| fprintf(where, |
| "%s: dup of where to stdout failed: %s (errno %d)\n", |
| __FUNCTION__, |
| strerror(errno), |
| errno); |
| fflush(where); |
| exit(1); |
| } |
| |
| /* redirect stderr to "where" */ |
| if (close(STDERR_FILENO) == -1) { |
| fprintf(where, |
| "%s: close of STDERR_FILENO failed: %s (errno %d)\n", |
| __FUNCTION__, |
| strerror(errno), |
| errno); |
| fflush(where); |
| exit(1); |
| } |
| |
| if (dup(fileno(where)) == -1) { |
| fprintf(where, |
| "%s: dup of where to stderr failed: %s (errno %d)\n", |
| __FUNCTION__, |
| strerror(errno), |
| errno); |
| fflush(where); |
| exit(1); |
| } |
| |
| #else |
| |
| /* Hopefully, by closing stdout & stderr, the subsequent fopen calls |
| will get mapped to the correct std handles. */ |
| fclose(stdout); |
| |
| if ((where = fopen(FileName, "w")) == NULL) { |
| perror("netserver: fopen of debug file as new stdout failed!"); |
| exit(1); |
| } |
| |
| fclose(stderr); |
| |
| if ((where = fopen(FileName, "w")) == NULL) { |
| fprintf(stdout, "fopen of debug file as new stderr failed!\n"); |
| exit(1); |
| } |
| |
| #endif |
| |
| } |
| |
| /* so, either we are a child of inetd in which case server_sock should |
| be stdin, or we are a child of a netserver parent. there will be |
| logic here for all of it, including Windows. it is important that |
| this be called before open_debug_file() */ |
| |
| void |
| set_server_sock() { |
| |
| if (debug) { |
| fprintf(where, |
| "%s: enter\n", |
| __FUNCTION__); |
| fflush(where); |
| } |
| |
| #ifdef WIN32 |
| server_sock = (SOCKET)GetStdHandle(STD_INPUT_HANDLE); |
| #elif !defined(__VMS) |
| if (server_sock != INVALID_SOCKET) { |
| fprintf(where,"Yo, Iz ain't invalid!\n"); |
| fflush(where); |
| exit(1); |
| } |
| |
| /* we dup this to up the reference count so when we do redirection |
| of the io streams we don't accidentally toast the control |
| connection in the case of our being a child of inetd. */ |
| server_sock = dup(0); |
| |
| #else |
| if ((server_sock = |
| socket(TCPIP$C_AUXS, SOCK_STREAM, 0)) == INVALID_SOCKET) { |
| fprintf(stderr, |
| "%s: failed to grab aux server socket: %s (errno %s)\n", |
| __FUNCTION__, |
| strerror(errno), |
| errno); |
| fflush(stderr); |
| exit(1); |
| } |
| #endif |
| |
| } |
| |
| |
| void |
| create_listens(char hostname[], char port[], int af) { |
| |
| struct addrinfo hints; |
| struct addrinfo *local_res; |
| struct addrinfo *local_res_temp; |
| int count, error; |
| int on = 1; |
| SOCKET temp_socket; |
| struct listen_elt *temp_elt; |
| |
| if (debug) { |
| fprintf(stderr, |
| "%s: called with host '%s' port '%s' family %s(%d)\n", |
| __FUNCTION__, |
| hostname, |
| port, |
| inet_ftos(af), |
| af); |
| fflush(stderr); |
| } |
| memset(&hints,0,sizeof(hints)); |
| hints.ai_family = af; |
| hints.ai_socktype = SOCK_STREAM; |
| hints.ai_protocol = IPPROTO_TCP; |
| hints.ai_flags = AI_PASSIVE; |
| |
| count = 0; |
| do { |
| error = getaddrinfo((char *)hostname, |
| (char *)port, |
| &hints, |
| &local_res); |
| count += 1; |
| if (error == EAI_AGAIN) { |
| if (debug) { |
| fprintf(stderr, |
| "%s: Sleeping on getaddrinfo EAI_AGAIN\n", |
| __FUNCTION__); |
| fflush(stderr); |
| } |
| sleep(1); |
| } |
| } while ((error == EAI_AGAIN) && (count <= 5)); |
| |
| if (error) { |
| if (debug) { |
| |
| fprintf(stderr, |
| "%s: could not resolve remote '%s' port '%s' af %d\n" |
| "\tgetaddrinfo returned %s (%d)\n", |
| __FUNCTION__, |
| hostname, |
| port, |
| af, |
| gai_strerror(error), |
| error); |
| |
| } |
| return; |
| } |
| |
| if (debug) { |
| dump_addrinfo(stderr, local_res, hostname, port, af); |
| } |
| |
| local_res_temp = local_res; |
| |
| while (local_res_temp != NULL) { |
| |
| temp_socket = socket(local_res_temp->ai_family,SOCK_STREAM,0); |
| |
| if (temp_socket == INVALID_SOCKET) { |
| if (debug) { |
| fprintf(stderr, |
| "%s could not allocate a socket: %s (errno %d)\n", |
| __FUNCTION__, |
| strerror(errno), |
| errno); |
| fflush(stderr); |
| } |
| local_res_temp = local_res_temp->ai_next; |
| continue; |
| } |
| |
| /* happiness and joy, keep going */ |
| if (setsockopt(temp_socket, |
| SOL_SOCKET, |
| SO_REUSEADDR, |
| (char *)&on , |
| sizeof(on)) == SOCKET_ERROR) { |
| if (debug) { |
| fprintf(stderr, |
| "%s: warning: could not set SO_REUSEADDR: %s (errno %d)\n", |
| __FUNCTION__, |
| strerror(errno), |
| errno); |
| fflush(stderr); |
| } |
| } |
| /* still happy and joyful */ |
| |
| if ((bind(temp_socket, |
| local_res_temp->ai_addr, |
| local_res_temp->ai_addrlen) != SOCKET_ERROR) && |
| (listen(temp_socket,1024) != SOCKET_ERROR)) { |
| |
| /* OK, now add to the list */ |
| temp_elt = (struct listen_elt *)malloc(sizeof(struct listen_elt)); |
| if (temp_elt) { |
| temp_elt->fd = temp_socket; |
| if (listen_list) { |
| temp_elt->next = listen_list; |
| } |
| else { |
| temp_elt->next = NULL; |
| } |
| listen_list = temp_elt; |
| } |
| else { |
| fprintf(stderr, |
| "%s: could not malloc a listen_elt\n", |
| __FUNCTION__); |
| fflush(stderr); |
| exit(1); |
| } |
| } |
| else { |
| /* we consider a bind() or listen() failure a transient and try |
| the next address */ |
| if (debug) { |
| fprintf(stderr, |
| "%s: warning: bind or listen call failure: %s (errno %d)\n", |
| __FUNCTION__, |
| strerror(errno), |
| errno); |
| fflush(stderr); |
| } |
| close(temp_socket); |
| } |
| local_res_temp = local_res_temp->ai_next; |
| } |
| |
| } |
| |
| void |
| setup_listens(char name[], char port[], int af) { |
| |
| int do_inet; |
| int no_name = 0; |
| #ifdef AF_INET6 |
| int do_inet6; |
| #endif |
| |
| if (debug) { |
| fprintf(where, |
| "%s: enter\n", |
| __FUNCTION__); |
| fflush(where); |
| } |
| |
| |
| if (strcmp(name,"") == 0) { |
| no_name = 1; |
| switch (af) { |
| case AF_UNSPEC: |
| do_inet = 1; |
| #ifdef AF_INET6 |
| do_inet6 = 1; |
| #endif |
| break; |
| case AF_INET: |
| do_inet = 1; |
| #ifdef AF_INET6 |
| do_inet6 = 0; |
| #endif |
| break; |
| #ifdef AF_INET6 |
| case AF_INET6: |
| do_inet = 0; |
| do_inet6 = 1; |
| break; |
| #endif |
| default: |
| do_inet = 1; |
| } |
| /* if we have IPv6, try that one first because it may be a superset */ |
| #ifdef AF_INET6 |
| if (do_inet6) |
| create_listens("::0",port,AF_INET6); |
| #endif |
| if (do_inet) |
| create_listens("0.0.0.0",port,AF_INET); |
| } |
| else { |
| create_listens(name,port,af); |
| } |
| |
| if (listen_list) { |
| fprintf(stdout, |
| "Starting netserver with host '%s' port '%s' and family %s\n", |
| (no_name) ? "IN(6)ADDR_ANY" : name, |
| port, |
| inet_ftos(af)); |
| fflush(stdout); |
| } |
| else { |
| fprintf(stderr, |
| "Unable to start netserver with '%s' port '%s' and family %s\n", |
| (no_name) ? "IN(6)ADDR_ANY" : name, |
| port, |
| inet_ftos(af)); |
| fflush(stderr); |
| exit(1); |
| } |
| } |
| |
| SOCKET |
| set_fdset(struct listen_elt *list, fd_set *fdset) { |
| |
| struct listen_elt *temp; |
| SOCKET max = INVALID_SOCKET; |
| |
| FD_ZERO(fdset); |
| |
| temp = list; |
| |
| if (debug) { |
| fprintf(where, |
| "%s: enter list %p fd_set %p\n", |
| __FUNCTION__, |
| list, |
| fdset); |
| fflush(where); |
| } |
| |
| while (temp) { |
| if (temp->fd > max) |
| max = temp->fd; |
| |
| if (debug) { |
| fprintf(where, |
| "setting %d in fdset\n", |
| temp->fd); |
| fflush(where); |
| } |
| |
| FD_SET(temp->fd,fdset); |
| |
| temp = temp->next; |
| } |
| |
| return max; |
| |
| } |
| |
| void |
| close_listens(struct listen_elt *list) { |
| struct listen_elt *temp; |
| |
| if (debug) { |
| fprintf(where, |
| "%s: enter\n", |
| __FUNCTION__); |
| fflush(where); |
| } |
| |
| temp = list; |
| |
| while (temp) { |
| close(temp->fd); |
| temp = temp->next; |
| } |
| } |
| |
| static int |
| recv_passphrase() { |
| |
| /* may need to revisit the timeout. we only respond if there is an |
| error with receiving the passphrase */ |
| if ((recv_request_timed_n(0,20) > 0) && |
| (netperf_request.content.request_type == PASSPHRASE) && |
| (!strcmp(passphrase, |
| (char *)netperf_request.content.test_specific_data))) { |
| /* it was okey dokey */ |
| return 0; |
| } |
| #if defined(SEND_PASSPHRASE_RESPONSE) |
| netperf_response.content.response_type = PASSPHRASE; |
| netperf_response.content.serv_errno = 403; |
| snprintf((char *)netperf_response.content.test_specific_data, |
| sizeof(netperf_response.content.test_specific_data), |
| "Sorry, unable to match with required passphrase\n"); |
| send_response_n(0); |
| #endif |
| fprintf(where, |
| "Unable to match required passphrase. Closing control connection\n"); |
| fflush(where); |
| |
| close(server_sock); |
| return -1; |
| } |
| |
| /* This routine implements the "main event loop" of the netperf server |
| code. Code above it will have set-up the control connection so it |
| can just merrily go about its business, which is to "schedule" |
| performance tests on the server. */ |
| |
| void |
| process_requests() |
| { |
| |
| float temp_rate; |
| |
| if (debug) { |
| fprintf(where, |
| "%s: enter\n", |
| __FUNCTION__); |
| fflush(where); |
| } |
| |
| /* if the netserver was started with a passphrase, look for it in |
| the first request to arrive. if there is no passphrase in the |
| first request we will end-up dumping the control connection. raj |
| 2012-01-23 */ |
| |
| if ((passphrase != NULL) && (recv_passphrase())) |
| return; |
| |
| while (1) { |
| |
| if (recv_request() <= 0) { |
| close(server_sock); |
| return; |
| } |
| |
| switch (netperf_request.content.request_type) { |
| |
| case DEBUG_ON: |
| netperf_response.content.response_type = DEBUG_OK; |
| if (!suppress_debug) { |
| debug++; |
| |
| if (debug == 1) { |
| /* we just flipped-on debugging, dump the request because |
| recv_request/recv_request_n will not have dumped it as its |
| dump_request() call is conditional on debug being set. raj |
| 2011-07-08 */ |
| dump_request(); |
| } |
| } |
| |
| send_response(); |
| break; |
| |
| case DEBUG_OFF: |
| if (debug) |
| debug--; |
| netperf_response.content.response_type = DEBUG_OK; |
| send_response(); |
| /* we used to take the trouble to close the debug file, but SAF |
| asked a good question when he asked "Why?" and since I cannot |
| think of a good reason, I have removed the code. raj |
| 2011-07-08 */ |
| break; |
| |
| case DO_SYSINFO: |
| { |
| netperf_response.content.response_type = SYSINFO_RESPONSE; |
| |
| snprintf((char *)netperf_response.content.test_specific_data, |
| sizeof(netperf_response.content.test_specific_data), |
| "%c%s%c%s%c%s%c%s", |
| ',', |
| "Deprecated", |
| ',' |
| , "Deprecated", |
| ',', |
| "Deprecated", |
| ',', |
| "Deprecated"); |
| |
| send_response_n(0); |
| break; |
| } |
| |
| case CPU_CALIBRATE: |
| netperf_response.content.response_type = CPU_CALIBRATE; |
| temp_rate = calibrate_local_cpu(0.0); |
| bcopy((char *)&temp_rate, |
| (char *)netperf_response.content.test_specific_data, |
| sizeof(temp_rate)); |
| bcopy((char *)&lib_num_loc_cpus, |
| (char *)netperf_response.content.test_specific_data + |
| sizeof(temp_rate), |
| sizeof(lib_num_loc_cpus)); |
| if (debug) { |
| fprintf(where, |
| "netserver: sending CPU information: rate is %g num cpu %d\n", |
| temp_rate, |
| lib_num_loc_cpus); |
| fflush(where); |
| } |
| |
| /* we need the cpu_start, cpu_stop in the looper case to kill |
| the child proceses raj 7/95 */ |
| |
| #ifdef USE_LOOPER |
| cpu_start(1); |
| cpu_stop(1,&temp_rate); |
| #endif /* USE_LOOPER */ |
| |
| send_response(); |
| break; |
| |
| case DO_TCP_STREAM: |
| recv_tcp_stream(); |
| break; |
| |
| case DO_TCP_MAERTS: |
| recv_tcp_maerts(); |
| break; |
| |
| case DO_TCP_RR: |
| recv_tcp_rr(); |
| break; |
| |
| case DO_TCP_CRR: |
| recv_tcp_conn_rr(); |
| break; |
| |
| case DO_TCP_CC: |
| recv_tcp_cc(); |
| break; |
| |
| #ifdef DO_1644 |
| case DO_TCP_TRR: |
| recv_tcp_tran_rr(); |
| break; |
| #endif /* DO_1644 */ |
| |
| #ifdef DO_NBRR |
| case DO_TCP_NBRR: |
| recv_tcp_nbrr(); |
| break; |
| #endif /* DO_NBRR */ |
| |
| case DO_UDP_STREAM: |
| recv_udp_stream(); |
| break; |
| |
| case DO_UDP_RR: |
| recv_udp_rr(); |
| break; |
| |
| #ifdef WANT_DLPI |
| |
| case DO_DLPI_CO_RR: |
| recv_dlpi_co_rr(); |
| break; |
| |
| case DO_DLPI_CL_RR: |
| recv_dlpi_cl_rr(); |
| break; |
| |
| case DO_DLPI_CO_STREAM: |
| recv_dlpi_co_stream(); |
| break; |
| |
| case DO_DLPI_CL_STREAM: |
| recv_dlpi_cl_stream(); |
| break; |
| |
| #endif /* WANT_DLPI */ |
| |
| #ifdef WANT_UNIX |
| |
| case DO_STREAM_STREAM: |
| recv_stream_stream(); |
| break; |
| |
| case DO_STREAM_RR: |
| recv_stream_rr(); |
| break; |
| |
| case DO_DG_STREAM: |
| recv_dg_stream(); |
| break; |
| |
| case DO_DG_RR: |
| recv_dg_rr(); |
| break; |
| |
| #endif /* WANT_UNIX */ |
| |
| #ifdef WANT_XTI |
| case DO_XTI_TCP_STREAM: |
| recv_xti_tcp_stream(); |
| break; |
| |
| case DO_XTI_TCP_RR: |
| recv_xti_tcp_rr(); |
| break; |
| |
| case DO_XTI_UDP_STREAM: |
| recv_xti_udp_stream(); |
| break; |
| |
| case DO_XTI_UDP_RR: |
| recv_xti_udp_rr(); |
| break; |
| |
| #endif /* WANT_XTI */ |
| |
| #ifdef WANT_SCTP |
| case DO_SCTP_STREAM: |
| recv_sctp_stream(); |
| break; |
| |
| case DO_SCTP_STREAM_MANY: |
| recv_sctp_stream_1toMany(); |
| break; |
| |
| case DO_SCTP_RR: |
| recv_sctp_rr(); |
| break; |
| |
| case DO_SCTP_RR_MANY: |
| recv_sctp_rr_1toMany(); |
| break; |
| #endif |
| |
| #ifdef WANT_SDP |
| case DO_SDP_STREAM: |
| recv_sdp_stream(); |
| break; |
| |
| case DO_SDP_MAERTS: |
| recv_sdp_maerts(); |
| break; |
| |
| case DO_SDP_RR: |
| recv_sdp_rr(); |
| break; |
| #endif |
| |
| #ifdef WANT_OMNI |
| case DO_OMNI: |
| recv_omni(); |
| break; |
| #endif |
| |
| case PASSPHRASE: |
| if (debug) { |
| fprintf(where,"Ignoring an unexpected passphrase control message\n"); |
| fflush(where); |
| } |
| break; |
| |
| default: |
| fprintf(where,"unknown test number %d\n", |
| netperf_request.content.request_type); |
| fflush(where); |
| netperf_response.content.serv_errno=998; |
| send_response(); |
| break; |
| |
| } |
| } |
| } |
| |
| /* the routine we call when we are going to spawn/fork/whatnot a child |
| process from the parent netserver daemon. raj 2011-07-08 */ |
| void |
| spawn_child() { |
| |
| #if defined(HAVE_FORK) |
| |
| if (debug) { |
| fprintf(where, |
| "%s: enter\n", |
| __FUNCTION__); |
| fflush(where); |
| } |
| |
| |
| /* flush the usual suspects */ |
| fflush(stdin); |
| fflush(stdout); |
| fflush(stderr); |
| fflush(where); |
| |
| signal(SIGCLD,SIG_IGN); |
| |
| switch (fork()) { |
| case -1: |
| fprintf(where, |
| "%s: fork() error %s (errno %d)\n", |
| __FUNCTION__, |
| strerror(errno), |
| errno); |
| fflush(where); |
| exit(1); |
| |
| case 0: |
| /* we are the child, but not of inetd. we don't know if we are |
| the child of a daemonized parent or not, so we still need to |
| worry about the standard file descriptors. raj 2011-07-11 */ |
| |
| close_listens(listen_list); |
| open_debug_file(); |
| |
| child = 1; |
| netperf_daemon = 0; |
| process_requests(); |
| exit(0); |
| break; |
| |
| default: |
| /* we are the parent, not a great deal to do here, but we may |
| want to reap some children */ |
| #if !defined(HAVE_SETSID) |
| /* Only call "waitpid()" if "setsid()" is not used. */ |
| while(waitpid(-1, NULL, WNOHANG) > 0) { |
| if (debug) { |
| fprintf(where, |
| "%s: reaped a child process\n", |
| __FUNCTION__); |
| } |
| } |
| #endif |
| break; |
| } |
| |
| #elif defined(WIN32) |
| |
| BOOL b; |
| char *cmdline; |
| int cmdline_length; |
| int cmd_index; |
| PROCESS_INFORMATION pi; |
| STARTUPINFO si; |
| int i; |
| |
| if (debug) { |
| fprintf(where, |
| "%s: enter\n", |
| __FUNCTION__); |
| fflush(where); |
| } |
| |
| |
| /* create the cmdline array based on strlen(program) + 80 chars */ |
| cmdline_length = strlen(program) + 80; |
| cmdline = malloc(cmdline_length + 1); // +1 for trailing null |
| |
| memset(&si, 0 , sizeof(STARTUPINFO)); |
| si.cb = sizeof(STARTUPINFO); |
| |
| /* Pass the server_sock as stdin for the new process. Hopefully |
| this will continue to be created with the OBJ_INHERIT |
| attribute. */ |
| si.hStdInput = (HANDLE)server_sock; |
| si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); |
| si.hStdError = GetStdHandle(STD_ERROR_HANDLE); |
| si.dwFlags = STARTF_USESTDHANDLES; |
| |
| /* Build cmdline for child process */ |
| strcpy(cmdline, program); |
| cmd_index = strlen(cmdline); |
| if (verbosity > 1) { |
| cmd_index += snprintf(&cmdline[cmd_index], |
| cmdline_length - cmd_index, |
| " -v %d", |
| verbosity); |
| } |
| for (i=0; i < debug; i++) { |
| cmd_index += snprintf(&cmdline[cmd_index], |
| cmdline_length - cmd_index, |
| " -d"); |
| } |
| cmd_index += snprintf(&cmdline[cmd_index], |
| cmdline_length - cmd_index, |
| " -I %x", |
| (int)(UINT_PTR)server_sock); |
| |
| /* are these -i settings even necessary? the command line scanning |
| does not seem to do anything with them */ |
| cmd_index += snprintf(&cmdline[cmd_index], |
| cmdline_length - cmd_index, |
| " -i %x", |
| (int)(UINT_PTR)server_control); |
| cmd_index += snprintf(&cmdline[cmd_index], |
| cmdline_length - cmd_index, |
| " -i %x", |
| (int)(UINT_PTR)where); |
| |
| b = CreateProcess(NULL, /* Application Name */ |
| cmdline, |
| NULL, /* Process security attributes */ |
| NULL, /* Thread security attributes */ |
| TRUE, /* Inherit handles */ |
| 0, /* Creation flags |
| PROCESS_QUERY_INFORMATION, */ |
| NULL, /* Enviornment */ |
| NULL, /* Current directory */ |
| &si, /* StartupInfo */ |
| &pi); |
| if (!b) |
| { |
| perror("CreateProcessfailure: "); |
| free(cmdline); /* even though we exit :) */ |
| exit(1); |
| } |
| |
| /* We don't need the thread or process handles any more; |
| let them go away on their own timeframe. */ |
| |
| CloseHandle(pi.hThread); |
| CloseHandle(pi.hProcess); |
| |
| /* the caller/parent will close server_sock */ |
| |
| free(cmdline); |
| |
| #else |
| |
| fprintf(where, |
| "%s called on platform which cannot spawn children\n", |
| __FUNCTION__); |
| fflush(where); |
| exit(1); |
| |
| #endif /* HAVE_FORK */ |
| } |
| |
| void |
| accept_connection(SOCKET listen_fd) { |
| |
| struct sockaddr_storage peeraddr; |
| netperf_socklen_t peeraddrlen; |
| #if defined(SO_KEEPALIVE) |
| int on = 1; |
| #endif |
| |
| if (debug) { |
| fprintf(where, |
| "%s: enter\n", |
| __FUNCTION__); |
| fflush(where); |
| } |
| |
| peeraddrlen = sizeof(peeraddr); |
| |
| /* while server_control is only used by the WIN32 path, but why |
| bother ifdef'ing it? and besides, do we *really* need knowledge |
| of server_control in the WIN32 case? do we have to tell the |
| child about *all* the listen endpoints? raj 2011-07-08 */ |
| server_control = listen_fd; |
| |
| if ((server_sock = accept(listen_fd, |
| (struct sockaddr *)&peeraddr, |
| &peeraddrlen)) == INVALID_SOCKET) { |
| fprintf(where, |
| "%s: accept failure: %s (errno %d)\n", |
| __FUNCTION__, |
| strerror(errno), |
| errno); |
| fflush(where); |
| exit(1); |
| } |
| |
| #if defined(SO_KEEPALIVE) |
| /* we are not terribly concerned if this does not work, it is merely |
| duct tape added to belts and suspenders. raj 2011-07-08 */ |
| setsockopt(server_sock, |
| SOL_SOCKET, |
| SO_KEEPALIVE, |
| (const char *)&on, |
| sizeof(on)); |
| #endif |
| |
| if (spawn_on_accept) { |
| spawn_child(); |
| /* spawn_child() only returns when we are the parent */ |
| close(server_sock); |
| } |
| else { |
| process_requests(); |
| } |
| } |
| |
| void |
| accept_connections() { |
| |
| fd_set read_fds, write_fds, except_fds; |
| SOCKET high_fd, candidate; |
| int num_ready; |
| |
| if (debug) { |
| fprintf(where, |
| "%s: enter\n", |
| __FUNCTION__); |
| fflush(where); |
| } |
| |
| while (1) { |
| |
| FD_ZERO(&write_fds); |
| FD_ZERO(&except_fds); |
| high_fd = set_fdset(listen_list,&read_fds); |
| |
| #if !defined(WIN32) |
| num_ready = select(high_fd + 1, |
| #else |
| num_ready = select(1, |
| #endif |
| &read_fds, |
| &write_fds, |
| &except_fds, |
| NULL); |
| |
| if (num_ready < 0) { |
| fprintf(where, |
| "%s: select failure: %s (errno %d)\n", |
| __FUNCTION__, |
| strerror(errno), |
| errno); |
| fflush(where); |
| exit(1); |
| } |
| |
| /* try to keep things simple */ |
| candidate = 0; |
| while ((num_ready) && (candidate <= high_fd)) { |
| if (FD_ISSET(candidate,&read_fds)) { |
| accept_connection(candidate); |
| FD_CLR(candidate,&read_fds); |
| num_ready--; |
| } |
| else { |
| candidate++; |
| } |
| } |
| } |
| } |
| |
| #ifndef WIN32 |
| #define SERVER_ARGS "DdfhL:n:Np:v:VZ:46" |
| #else |
| #define SERVER_ARGS "DdfhL:n:Np:v:VZ:46I:i:" |
| #endif |
| void |
| scan_netserver_args(int argc, char *argv[]) { |
| |
| int c; |
| char arg1[BUFSIZ], arg2[BUFSIZ]; |
| |
| if (debug) { |
| fprintf(where, |
| "%s: enter\n", |
| __FUNCTION__); |
| fflush(where); |
| } |
| |
| while ((c = getopt(argc, argv, SERVER_ARGS)) != EOF){ |
| switch (c) { |
| case '?': |
| case 'h': |
| print_netserver_usage(); |
| exit(1); |
| case 'd': |
| debug++; |
| suppress_debug = 0; |
| break; |
| case 'D': |
| /* perhaps one of these days we'll take an argument */ |
| want_daemonize = 0; |
| not_inetd = 1; |
| break; |
| case 'f': |
| spawn_on_accept = 0; |
| not_inetd = 1; |
| break; |
| #ifdef WIN32 |
| case 'I': |
| child = TRUE; |
| break; |
| case 'i': |
| break; |
| #endif |
| case 'L': |
| not_inetd = 1; |
| break_args_explicit(optarg,arg1,arg2); |
| if (arg1[0]) { |
| strncpy(local_host_name,arg1,sizeof(local_host_name)); |
| } |
| if (arg2[0]) { |
| local_address_family = parse_address_family(arg2); |
| } |
| break; |
| case 'n': |
| shell_num_cpus = atoi(optarg); |
| if (shell_num_cpus > MAXCPUS) { |
| fprintf(stderr, |
| "netserver: This version can only support %d CPUs. Please" |
| "increase MAXCPUS in netlib.h and recompile.\n", |
| MAXCPUS); |
| fflush(stderr); |
| exit(1); |
| } |
| break; |
| case 'N': |
| suppress_debug = 1; |
| debug = 0; |
| break; |
| case 'p': |
| /* we want to open a listen socket at a specified port number */ |
| strncpy(listen_port,optarg,sizeof(listen_port)); |
| not_inetd = 1; |
| break; |
| case 'Z': |
| /* only copy as much of the passphrase as could fit in the |
| test-specific portion of a control message. Windows does not |
| seem to have a strndup() so just malloc and strncpy it. we |
| weren't checking the strndup() return so won't bother with |
| checking malloc(). we will though make certain we only |
| allocated it once in the event that someone puts -Z on the |
| command line more than once */ |
| if (passphrase == NULL) |
| passphrase = malloc(sizeof(netperf_request.content.test_specific_data)); |
| strncpy(passphrase, |
| optarg, |
| sizeof(netperf_request.content.test_specific_data)); |
| passphrase[sizeof(netperf_request.content.test_specific_data) - 1] = '\0'; |
| break; |
| case '4': |
| local_address_family = AF_INET; |
| break; |
| case '6': |
| #if defined(AF_INET6) |
| local_address_family = AF_INET6; |
| #else |
| local_address_family = AF_UNSPEC; |
| #endif |
| break; |
| case 'v': |
| /* say how much to say */ |
| verbosity = atoi(optarg); |
| break; |
| case 'V': |
| printf("Netperf version %s\n",NETPERF_VERSION); |
| exit(0); |
| break; |
| |
| } |
| } |
| } |
| |
| void |
| daemonize() { |
| #if defined(HAVE_FORK) |
| |
| if (debug) { |
| fprintf(where, |
| "%s: enter\n", |
| __FUNCTION__); |
| fflush(where); |
| } |
| |
| /* flush the usual suspects */ |
| fflush(stdin); |
| fflush(stdout); |
| fflush(stderr); |
| |
| switch (fork()) { |
| case -1: |
| fprintf(stderr, |
| "%s: fork() error %s (errno %d)\n", |
| __FUNCTION__, |
| strerror(errno), |
| errno); |
| fflush(stderr); |
| exit(1); |
| case 0: |
| |
| /* perhaps belt and suspenders, but if we dump core, perhaps |
| better to do so here. we won't worry about the call being |
| successful though. raj 2011-07-08 */ |
| chdir(DEBUG_LOG_FILE_DIR); |
| |
| /* we are the child. we should get a new "where" to match our new |
| pid */ |
| |
| open_debug_file(); |
| |
| #ifdef HAVE_SETSID |
| setsid(); |
| #else |
| setpgrp(); |
| #endif /* HAVE_SETSID */ |
| |
| signal(SIGCLD, SIG_IGN); |
| |
| /* ok, we can start accepting control connections now */ |
| accept_connections(); |
| |
| default: |
| /* we are the parent, nothing to do but exit? */ |
| exit(0); |
| } |
| |
| #else |
| fprintf(where, |
| "%s called on platform which cannot daemonize\n", |
| __FUNCTION__); |
| fflush(where); |
| exit(1); |
| #endif /* HAVE_FORK */ |
| } |
| |
| static void |
| check_if_inetd() { |
| |
| if (debug) { |
| fprintf(where, |
| "%s: enter\n", |
| __FUNCTION__); |
| fflush(where); |
| } |
| |
| if (not_inetd) { |
| return; |
| } |
| else { |
| #if !defined(WIN32) && !defined(__VMS) |
| struct sockaddr_storage name; |
| netperf_socklen_t namelen; |
| |
| namelen = sizeof(name); |
| if (getsockname(0, |
| (struct sockaddr *)&name, |
| &namelen) == SOCKET_ERROR) { |
| not_inetd = 1; |
| } |
| else { |
| not_inetd = 0; |
| child = 1; |
| } |
| #endif |
| } |
| } |
| |
| /* OK, so how does all this work you ask? Well, we are in a maze of |
| twisty options, all different. Netserver can be invoked as a child |
| of inetd or the VMS auxiliary server process, or a parent netserver |
| process. In those cases, we could/should follow the "child" |
| path. However, there are really two "child" paths through the |
| netserver code. |
| |
| When this netserver is a child of a parent netserver in the |
| case of *nix, the child process will be created by a |
| spawn_child_process() in accept_connections() and will not hit the |
| "child" path here in main(). |
| |
| When this netserver is a child of a parent netserver in the case of |
| windows, the child process will have been spawned via a |
| Create_Process() call in spawn_child_process() in |
| accept_connections, but will flow through here again. We rely on |
| the scan_netserver_args() call to have noticed the magic option |
| that tells us we are a child process. |
| |
| When this netserver is launched from the command line we will first |
| set-up the listen endpoint(s) for the controll connection. At that |
| point we decide if we want to and can become our own daemon, or |
| stay attached to the "terminal." When this happens under *nix, we |
| will again take a fork() path via daemonize() and will not come |
| back through main(). If we ever learn how to become our own daemon |
| under Windows, we will undoubtedly take a Create_Process() path |
| again and will come through main() once again - that is what the |
| "daemon" case here is all about. |
| |
| It is hoped that this is all much clearer than the old spaghetti |
| code that netserver had become. raj 2011-07-11 */ |
| |
| |
| int _cdecl |
| main(int argc, char *argv[]) { |
| |
| #ifdef WIN32 |
| WSADATA wsa_data ; |
| |
| /* Initialize the winsock lib do we still want version 2.2? */ |
| if ( WSAStartup(MAKEWORD(2,2), &wsa_data) == SOCKET_ERROR ){ |
| printf("WSAStartup() failed : %lu\n", GetLastError()) ; |
| return -1 ; |
| } |
| #endif /* WIN32 */ |
| |
| /* Save away the program name */ |
| program = (char *)malloc(strlen(argv[0]) + 1); |
| if (program == NULL) { |
| printf("malloc for program name failed!\n"); |
| return -1 ; |
| } |
| strcpy(program, argv[0]); |
| |
| init_netserver_globals(); |
| |
| netlib_init(); |
| |
| strncpy(local_host_name,"",sizeof(local_host_name)); |
| local_address_family = AF_UNSPEC; |
| strncpy(listen_port,TEST_PORT,sizeof(listen_port)); |
| |
| scan_netserver_args(argc, argv); |
| |
| check_if_inetd(); |
| |
| if (child) { |
| /* we are the child of either an inetd or parent netserver via |
| spawning (Windows) rather than fork()ing. if we were fork()ed |
| we would not be coming through this way. set_server_sock() must |
| be called before open_debug_file() or there is a chance that |
| we'll toast the descriptor when we do not wish it. */ |
| set_server_sock(); |
| open_debug_file(); |
| process_requests(); |
| } |
| else if (daemon_parent) { |
| /* we are the parent daemonized netserver |
| process. accept_connections() will decide if we want to spawn a |
| child process */ |
| accept_connections(); |
| } |
| else { |
| /* we are the top netserver process, so we have to create the |
| listen endpoint(s) and decide if we want to daemonize */ |
| setup_listens(local_host_name,listen_port,local_address_family); |
| if (want_daemonize) { |
| daemonize(); |
| } |
| accept_connections(); |
| } |
| |
| unlink_empty_debug_file(); |
| |
| #ifdef WIN32 |
| WSACleanup(); |
| #endif |
| |
| return 0; |
| |
| } |