Home | History | Annotate | Download | only in ns-tools
      1 /******************************************************************************/
      2 /*                                                                            */
      3 /*   Copyright (c) International Business Machines  Corp., 2005               */
      4 /*                                                                            */
      5 /*   This program is free software;  you can redistribute it and/or modify    */
      6 /*   it under the terms of the GNU General Public License as published by     */
      7 /*   the Free Software Foundation; either version 2 of the License, or        */
      8 /*   (at your option) any later version.                                      */
      9 /*                                                                            */
     10 /*   This program is distributed in the hope that it will be useful,          */
     11 /*   but WITHOUT ANY WARRANTY;  without even the implied warranty of          */
     12 /*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See                */
     13 /*   the GNU General Public License for more details.                         */
     14 /*                                                                            */
     15 /*   You should have received a copy of the GNU General Public License        */
     16 /*   along with this program;  if not, write to the Free Software             */
     17 /*   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA  */
     18 /*                                                                            */
     19 /******************************************************************************/
     20 
     21 /*
     22  * File:
     23  *	ns-tcpserver.c
     24  *
     25  * Description:
     26  *      This is TCP traffic server.
     27  *	Accept connections from the clients, then send tcp segments to clients
     28  *
     29  * Author:
     30  *	Mitsuru Chinen <mitch (at) jp.ibm.com>
     31  *
     32  * History:
     33  *	Oct 19 2005 - Created (Mitsuru Chinen)
     34  *---------------------------------------------------------------------------*/
     35 
     36 #include "ns-traffic.h"
     37 
     38 /*
     39  * Standard Include Files
     40  */
     41 #include <stdio.h>
     42 #include <stdlib.h>
     43 #include <string.h>
     44 #include <errno.h>
     45 #include <fcntl.h>
     46 #include <netdb.h>
     47 #include <time.h>
     48 #include <unistd.h>
     49 #include <sys/select.h>
     50 #include <sys/socket.h>
     51 #include <sys/stat.h>
     52 #include <sys/types.h>
     53 #include <sys/wait.h>
     54 #include <netinet/in.h>
     55 #include <netinet/tcp.h>
     56 
     57 /*
     58  * Gloval variables
     59  */
     60 struct sigaction handler;	/* Behavior for a signal */
     61 int catch_sighup;		/* When catch the SIGHUP, set to non-zero */
     62 int catch_sigpipe;		/* When catch the SIGPIPE, set to non-zero */
     63 
     64 /*
     65  * Structure: server_info
     66  *
     67  * Description:
     68  *  This structure stores the information of a server
     69  */
     70 struct server_info {
     71 	sa_family_t family;	/* protocol family */
     72 	char *portnum;		/* port number */
     73 	int listen_sd;		/* socket descriptor for listening */
     74 	int concurrent;		/* if non-zero, act as a concurrent server */
     75 	size_t current_connection;	/* number of the current connection */
     76 	size_t max_connection;	/* maximum connection number */
     77 	size_t lost_connection;	/* number of lost connection */
     78 	size_t small_sending;	/* if non-zero, in the small sending mode */
     79 	size_t window_scaling;	/* if non-zero, in the window scaling mode */
     80 };
     81 
     82 /*
     83  * Function: usage()
     84  *
     85  * Descripton:
     86  *  Print the usage of this program. Then, terminate this program with
     87  *  the specified exit value.
     88  *
     89  * Argument:
     90  *  exit_value:	exit value
     91  *
     92  * Return value:
     93  *  This function does not return.
     94  */
     95 void usage(char *program_name, int exit_value)
     96 {
     97 	FILE *stream = stdout;	/* stream where the usage is output */
     98 
     99 	if (exit_value == EXIT_FAILURE)
    100 		stream = stderr;
    101 
    102 	fprintf(stream, "%s [OPTION]\n"
    103 		"\t-f\tprotocol family\n"
    104 		"\t\t  4 : IPv4\n"
    105 		"\t\t  6 : IPv6\n"
    106 		"\t-p\tport number\n"
    107 		"\t-b\twork in the background\n"
    108 		"\t-c\twork in the concurrent server mode\n"
    109 		"\t-s\twork in the small sending mode\n"
    110 		"\t-w\twork in the window scaling mode\n"
    111 		"\t-o\tfilename where the server infomation is outputted\n"
    112 		"\t-d\twork in the debug mode\n"
    113 		"\t-h\tdisplay this usage\n"
    114 		"" "*) Server works till it receives SIGHUP\n", program_name);
    115 	exit(exit_value);
    116 }
    117 
    118 /*
    119  * Function: set_signal_flag()
    120  *
    121  * Description:
    122  *  This function sets global variable according to the signal.
    123  *  Once a signal is caught, the signal is ignored after that.
    124  *
    125  * Argument:
    126  *  type: type of signal
    127  *
    128  * Return value:
    129  *  None
    130  */
    131 void set_signal_flag(int type)
    132 {
    133 	/* Set SIG_IGN against the caught signal */
    134 	handler.sa_handler = SIG_IGN;
    135 	if (sigaction(type, &handler, NULL) < 0)
    136 		fatal_error("sigaction()");
    137 
    138 	if (debug)
    139 		fprintf(stderr, "Catch signal. type is %d\n", type);
    140 
    141 	switch (type) {
    142 	case SIGHUP:
    143 		catch_sighup = 1;
    144 		break;
    145 	case SIGPIPE:
    146 		catch_sigpipe = 1;
    147 		break;
    148 	default:
    149 		fprintf(stderr, "Unexpected signal (%d) is caught\n", type);
    150 		exit(EXIT_FAILURE);
    151 	}
    152 }
    153 
    154 /*
    155  * Function: delete_zombies()
    156  *
    157  * Descripton:
    158  *  Delete the zombies
    159  *
    160  * Argument:
    161  *  info_p:	pointer to a server infomation
    162  *
    163  * Return value:
    164  *  None
    165  */
    166 void delete_zombies(struct server_info *info_p)
    167 {
    168 	int status;		/* exit value of a child */
    169 	pid_t zombie_pid;	/* process id of a zombie */
    170 
    171 	while (info_p->current_connection) {
    172 		zombie_pid = waitpid((pid_t) - 1, &status, WNOHANG);
    173 		if (zombie_pid == (pid_t) - 1)
    174 			fatal_error("waitpid()");
    175 		else if (zombie_pid == (pid_t) 0)
    176 			break;
    177 		else {
    178 			--info_p->current_connection;
    179 			if (status != EXIT_SUCCESS) {
    180 				++info_p->lost_connection;
    181 				if (debug)
    182 					fprintf(stderr,
    183 						"The number of lost conncections is %zu\n",
    184 						info_p->lost_connection);
    185 			}
    186 		}
    187 	}
    188 }
    189 
    190 /*
    191  * Function: create_listen_socket()
    192  *
    193  * Descripton:
    194  *  Create a socket to listen for connections on a socket.
    195  *  The socket discripter is stored info_p->listen_sd.
    196  *
    197  * Argument:
    198  *  info_p:	pointer to a server infomation
    199  *
    200  * Return value:
    201  *  None
    202  */
    203 void create_listen_socket(struct server_info *info_p)
    204 {
    205 	int on;			/* on/off at an socket option */
    206 	int err;		/* return value of getaddrinfo */
    207 	struct addrinfo hints;	/* hints for getaddrinfo() */
    208 	struct addrinfo *res;	/* pointer to addrinfo */
    209 
    210 	/* Set the hints to addrinfo() */
    211 	memset(&hints, '\0', sizeof(struct addrinfo));
    212 	hints.ai_family = info_p->family;
    213 	hints.ai_socktype = SOCK_STREAM;
    214 	hints.ai_protocol = IPPROTO_TCP;
    215 	hints.ai_flags = AI_PASSIVE;
    216 
    217 	/* Translate the network and service information of the server */
    218 	err = getaddrinfo(NULL, info_p->portnum, &hints, &res);
    219 	if (err) {
    220 		fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(err));
    221 		exit(EXIT_FAILURE);
    222 	}
    223 	if (res->ai_next) {
    224 		fprintf(stderr, "getaddrinfo(): multiple address is found.");
    225 		exit(EXIT_FAILURE);
    226 	}
    227 
    228 	/* Create a socket for listening. */
    229 	info_p->listen_sd = socket(res->ai_family,
    230 				   res->ai_socktype, res->ai_protocol);
    231 	if (info_p->listen_sd < 0)
    232 		fatal_error("socket()");
    233 
    234 #ifdef IPV6_V6ONLY
    235 	/* Don't accept IPv4 mapped address if the protocol family is IPv6 */
    236 	if (res->ai_family == PF_INET6) {
    237 		on = 1;
    238 		if (setsockopt(info_p->listen_sd,
    239 			       IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(int)))
    240 			fatal_error("setsockopt()");
    241 	}
    242 #endif
    243 
    244 	/* Enable to reuse the socket */
    245 	on = 1;
    246 	if (setsockopt(info_p->listen_sd,
    247 		       SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)))
    248 		fatal_error("setsockopt()");
    249 
    250 	/* Disable the Nagle algorithm, when small sending mode */
    251 	if (info_p->small_sending) {
    252 		on = 1;
    253 		if (setsockopt(info_p->listen_sd,
    254 			       IPPROTO_TCP, TCP_NODELAY, &on, sizeof(int)))
    255 			fatal_error("setsockopt()");
    256 		if (debug) {
    257 			fprintf(stderr, "small sending[on]\n");
    258 		}
    259 	}
    260 
    261 	/* Maximize socket buffer, when window scaling mode */
    262 	if (info_p->window_scaling)
    263 		maximize_sockbuf(info_p->listen_sd);
    264 
    265 	/* Bind to the local address */
    266 	if (bind(info_p->listen_sd, res->ai_addr, res->ai_addrlen) < 0)
    267 		fatal_error("bind()");
    268 	freeaddrinfo(res);
    269 
    270 	/* Start to listen for connections */
    271 	if (listen(info_p->listen_sd, 5) < 0)
    272 		fatal_error("listen()");
    273 }
    274 
    275 /*
    276  * Function: communicate_client()
    277  *
    278  * Descripton:
    279  *  Communicate with the connected client.
    280  *  Currently, this function sends tcp segment in the specified second
    281  *  or recevie SIGHUP
    282  *
    283  * Argument:
    284  *  sock_fd: socket descriptor to communicate with client
    285  *  info_p:  pointer to a server infomation
    286  *
    287  * Return value:
    288  *  0:	    success
    289  *  other:  fail
    290  */
    291 int communicate_client(struct server_info *info_p, int sock_fd)
    292 {
    293 	char *sendmsg;		/* pointer to the message to send */
    294 	int sndbuf_size;	/* size of the send buffer */
    295 	socklen_t sock_optlen;	/* size of the result parameter */
    296 	ssize_t sntbyte_size;	/* size of the sent byte */
    297 	int ret = EXIT_SUCCESS;	/* The return value of this function */
    298 
    299 	if (info_p->small_sending) {	/* small sending mode */
    300 		sndbuf_size = 1;
    301 	} else {
    302 		sock_optlen = sizeof(sndbuf_size);
    303 		if (getsockopt
    304 		    (sock_fd, SOL_SOCKET, SO_SNDBUF, &sndbuf_size,
    305 		     &sock_optlen) < 0) {
    306 			perror("getsockopt()");
    307 			if (close(sock_fd))
    308 				fatal_error("close()");
    309 			return EXIT_FAILURE;
    310 		}
    311 	}
    312 	if (debug)
    313 		fprintf(stderr, "sndbuf size is %d\n", sndbuf_size);
    314 
    315 	/* Define the message */
    316 	sendmsg = malloc(sndbuf_size);
    317 	if (sendmsg == NULL) {
    318 		fprintf(stderr, "malloc() is failed.\n");
    319 		if (close(sock_fd))
    320 			fatal_error("close()");
    321 		return EXIT_FAILURE;
    322 	}
    323 
    324 	/* Set a signal handler against SIGHUP and SIGPIPE */
    325 	handler.sa_handler = set_signal_flag;
    326 	if (sigaction(SIGHUP, &handler, NULL) < 0)
    327 		fatal_error("sigaction()");
    328 	if (sigaction(SIGPIPE, &handler, NULL) < 0)
    329 		fatal_error("sigaction()");
    330 
    331 	/* Send the message */
    332 	for (;;) {
    333 		sntbyte_size = send(sock_fd, sendmsg, sndbuf_size, 0);
    334 
    335 		/* Catch SIGPIPE */
    336 		if (catch_sigpipe) {
    337 			if (debug)
    338 				fprintf(stderr,
    339 					"The client closed the connection.\n");
    340 			break;
    341 		}
    342 
    343 		/* Catch SIGHUP */
    344 		if (catch_sighup)
    345 			break;
    346 
    347 		if (sntbyte_size < (ssize_t) 0) {
    348 			if (errno == EPIPE) {
    349 				if (debug)
    350 					fprintf(stderr,
    351 						"The client closed the connection.\n");
    352 			} else {
    353 				printf("errno=%d\n", errno);
    354 				perror("send()");
    355 				ret = EXIT_FAILURE;
    356 			}
    357 			break;
    358 		}
    359 	}
    360 
    361 	free(sendmsg);
    362 	if (close(sock_fd))
    363 		fatal_error("close()");
    364 	return ret;
    365 }
    366 
    367 /*
    368  * Function: handle_client()
    369  *
    370  * Descripton:
    371  *  Accept a connection from a client, then fork to communicate the client
    372  *
    373  * Argument:
    374  *  info_p:	pointer to a server infomation
    375  *
    376  * Return value:
    377  *  0:	    success
    378  *  other:  fail
    379  */
    380 int handle_client(struct server_info *info_p)
    381 {
    382 	int ret = EXIT_SUCCESS;	/* return value of this function */
    383 	int do_accept = 1;	/* if non-zero, accept connection */
    384 	fd_set read_fds;	/* list of file descriptor for reading */
    385 	int max_read_fd = 0;	/* maximum number in the read fds */
    386 
    387 	info_p->current_connection = 0;
    388 	FD_ZERO(&read_fds);
    389 	FD_SET(info_p->listen_sd, &read_fds);
    390 	max_read_fd = info_p->listen_sd;
    391 
    392 	/* Catch SIGHUP */
    393 	handler.sa_handler = set_signal_flag;
    394 	if (sigaction(SIGHUP, &handler, NULL) < 0)
    395 		fatal_error("sigaction()");
    396 
    397 	/* Loop to wait a new connection */
    398 	for (;;) {
    399 		if (do_accept) {
    400 			int data_sd;	/* socket descriptor for send/recv data */
    401 			socklen_t client_addr_len;	/* length of `client_addr' */
    402 			struct sockaddr_storage client_addr;	/* address of a client */
    403 			int select_ret;	/* return value of select() */
    404 			fd_set active_fds;	/* list of the active file descriptor */
    405 			struct timeval select_timeout;	/* timeout for select() */
    406 
    407 			/* When catch SIGHUP, no more connection is acceptted. */
    408 			if (catch_sighup) {
    409 				do_accept = 0;
    410 				if (close(info_p->listen_sd))
    411 					fatal_error("close()");
    412 				continue;
    413 			}
    414 
    415 			/* Check a connection is requested */
    416 			active_fds = read_fds;
    417 			select_timeout.tv_sec = 0;	/* 0.5 sec */
    418 			select_timeout.tv_usec = 500000;
    419 
    420 			select_ret = select(max_read_fd + 1,
    421 					    &active_fds, NULL, NULL,
    422 					    &select_timeout);
    423 			if (select_ret < 0) {
    424 				do_accept = 0;
    425 				if (!catch_sighup) {
    426 					perror("select()");
    427 					ret = EXIT_FAILURE;
    428 				}
    429 				if (close(info_p->listen_sd))
    430 					fatal_error("close()");
    431 				continue;
    432 			} else if (select_ret == 0) {	/* select() is timeout */
    433 				if (info_p->concurrent)
    434 					delete_zombies(info_p);
    435 				continue;
    436 			}
    437 
    438 			/* Accetpt a client connection */
    439 			if (FD_ISSET(info_p->listen_sd, &active_fds)) {
    440 				client_addr_len =
    441 				    sizeof(struct sockaddr_storage);
    442 				data_sd =
    443 				    accept(info_p->listen_sd,
    444 					   (struct sockaddr *)&client_addr,
    445 					   &client_addr_len);
    446 				if (data_sd < 0) {
    447 					do_accept = 0;
    448 					if (!catch_sighup) {
    449 						perror("accept()");
    450 						ret = EXIT_FAILURE;
    451 					}
    452 					if (close(info_p->listen_sd))
    453 						fatal_error("close()");
    454 					continue;
    455 				}
    456 				if (debug)
    457 					fprintf(stderr,
    458 						"called accept(). data_sd=%d\n",
    459 						data_sd);
    460 
    461 				/* Handle clients */
    462 				if (info_p->concurrent) {	/* concurrent server. */
    463 					pid_t child_pid;
    464 					child_pid = fork();
    465 					if (child_pid < 0) {	/* fork() is failed. */
    466 						perror("fork()");
    467 						if (close(data_sd))
    468 							fatal_error("close()");
    469 						if (close(info_p->listen_sd))
    470 							fatal_error("close()");
    471 						do_accept = 0;
    472 						continue;
    473 					} else if (child_pid == 0) {	/* case of a child */
    474 						int exit_value;
    475 						if (close(info_p->listen_sd))
    476 							fatal_error("close()");
    477 						exit_value =
    478 						    communicate_client(info_p,
    479 								       data_sd);
    480 						if (debug)
    481 							fprintf(stderr,
    482 								"child(%d) exits. value is %d\n",
    483 								getpid(),
    484 								exit_value);
    485 						exit(exit_value);
    486 					} else {	/* case of the parent */
    487 						if (close(data_sd))
    488 							fatal_error("close()");
    489 
    490 						++info_p->current_connection;
    491 						if (info_p->max_connection <
    492 						    info_p->
    493 						    current_connection) {
    494 							info_p->max_connection =
    495 							    info_p->
    496 							    current_connection;
    497 							if (debug)
    498 								fprintf(stderr,
    499 									"The maximum connection is updated. The number is %zu.\n",
    500 									info_p->
    501 									max_connection);
    502 						}
    503 						delete_zombies(info_p);
    504 					}
    505 				} else {	/* repeat server */
    506 					ret =
    507 					    communicate_client(info_p, data_sd);
    508 					if (ret != EXIT_SUCCESS)
    509 						if (close(info_p->listen_sd))
    510 							fatal_error("close()");
    511 					break;
    512 				}
    513 			}
    514 		} else {
    515 			/* case where new connection isn't accepted. */
    516 			if (info_p->concurrent)
    517 				delete_zombies(info_p);
    518 			if (info_p->current_connection == 0)
    519 				break;
    520 		}
    521 	}
    522 	return ret;
    523 }
    524 
    525 /*
    526  *
    527  *  Function: main()
    528  *
    529  */
    530 int main(int argc, char *argv[])
    531 {
    532 	char *program_name = argv[0];
    533 	int optc;		/* option */
    534 	struct server_info server;	/* server information */
    535 	int ret = EXIT_SUCCESS;	/* exit value */
    536 	int background = 0;	/* If non-zero work in the background */
    537 	FILE *info_fp = stdout;	/* FILE pointer to a information file */
    538 
    539 	debug = 0;
    540 
    541 	/* Initilalize the server information */
    542 	memset(&server, '\0', sizeof(struct server_info));
    543 	server.family = PF_UNSPEC;
    544 	server.portnum = NULL;
    545 
    546 	/* Retrieve the options */
    547 	while ((optc = getopt(argc, argv, "f:p:bcswo:dh")) != EOF) {
    548 		switch (optc) {
    549 		case 'f':
    550 			if (strncmp(optarg, "4", 1) == 0)
    551 				server.family = PF_INET;	/* IPv4 */
    552 			else if (strncmp(optarg, "6", 1) == 0)
    553 				server.family = PF_INET6;	/* IPv6 */
    554 			else {
    555 				fprintf(stderr,
    556 					"protocol family should be 4 or 6.\n");
    557 				usage(program_name, EXIT_FAILURE);
    558 			}
    559 			break;
    560 
    561 		case 'p':
    562 			{
    563 				unsigned long int num;
    564 				num = strtoul(optarg, NULL, 0);
    565 				if (num < PORTNUMMIN || PORTNUMMAX < num) {
    566 					fprintf(stderr,
    567 						"The range of port is from %u to %u\n",
    568 						PORTNUMMIN, PORTNUMMAX);
    569 					usage(program_name, EXIT_FAILURE);
    570 				}
    571 				server.portnum = strdup(optarg);
    572 			}
    573 			break;
    574 
    575 		case 'b':
    576 			background = 1;
    577 			break;
    578 
    579 		case 'c':
    580 			server.concurrent = 1;
    581 			break;
    582 
    583 		case 's':
    584 			server.small_sending = 1;
    585 			break;
    586 
    587 		case 'w':
    588 			server.window_scaling = 1;
    589 			break;
    590 
    591 		case 'o':
    592 			if ((info_fp = fopen(optarg, "w")) == NULL) {
    593 				fprintf(stderr, "Cannot open %s\n", optarg);
    594 				exit(EXIT_FAILURE);
    595 			}
    596 			break;
    597 
    598 		case 'd':
    599 			debug = 1;
    600 			break;
    601 
    602 		case 'h':
    603 			usage(program_name, EXIT_SUCCESS);
    604 			break;
    605 
    606 		default:
    607 			usage(program_name, EXIT_FAILURE);
    608 		}
    609 	}
    610 
    611 	/* Check the family is spefied. */
    612 	if (server.family == PF_UNSPEC) {
    613 		fprintf(stderr, "protocol family should be specified.\n");
    614 		usage(program_name, EXIT_FAILURE);
    615 	}
    616 
    617 	/* Check the port number is specfied. */
    618 	if (server.portnum == NULL) {
    619 		server.portnum = (char *)calloc(6, sizeof(char));
    620 		sprintf(server.portnum, "%u", PORTNUMMIN);
    621 	}
    622 
    623 	/* If -b option is specified, work as a daemon */
    624 	if (background)
    625 		if (daemon(0, 0) < 0)
    626 			fatal_error("daemon()");
    627 
    628 	/* At first, SIGHUP is ignored. default with SIGPIPE */
    629 	handler.sa_handler = SIG_IGN;
    630 	if (sigfillset(&handler.sa_mask) < 0)
    631 		fatal_error("sigfillset()");
    632 	handler.sa_flags = 0;
    633 
    634 	if (sigaction(SIGHUP, &handler, NULL) < 0)
    635 		fatal_error("sigaction()");
    636 
    637 	/* Create a listen socket */
    638 	create_listen_socket(&server);
    639 
    640 	/* Output any server information to the information file */
    641 	fprintf(info_fp, "PID: %u\n", getpid());
    642 	fflush(info_fp);
    643 	if (info_fp != stdout)
    644 		if (fclose(info_fp))
    645 			fatal_error("fclose()");
    646 
    647 	/* Handle one or more tcp clients. */
    648 	ret = handle_client(&server);
    649 	exit(ret);
    650 }
    651