/* 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 # endif # include #endif #if HAVE_STRINGS_H # include #endif #if HAVE_LIMITS_H # include #endif #if HAVE_SYS_IPC_H #include #endif #if HAVE_SYS_IOCTL_H #include #endif #if HAVE_SYS_SOCKET_H #include #endif #if HAVE_SYS_STAT_H #include #endif #if HAVE_NETINET_IN_H #include #endif #if HAVE_NETDB_H #include #endif #if HAVE_UNISTD_H #include #endif #if HAVE_STDLIB_H #include #endif #if HAVE_ERRNO_H #include #endif #if HAVE_SIGNAL_H #include /* 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 #endif #endif #ifdef WIN32 #include #include #if HAVE_WS2TCPIP_H #include #endif #include #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 #include #include /* 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; }