• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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@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  */
usage(char * program_name,int exit_value)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  */
set_signal_flag(int type)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  */
delete_zombies(struct server_info * info_p)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  */
create_listen_socket(struct server_info * info_p)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  */
communicate_client(struct server_info * info_p,int sock_fd)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  */
handle_client(struct server_info * info_p)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  */
main(int argc,char * argv[])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