Home | History | Annotate | Line # | Download | only in faithd
tcp.c revision 1.1
      1 /*	$NetBSD: tcp.c,v 1.1 1999/07/13 22:16:49 itojun Exp $	*/
      2 
      3 /*
      4  * Copyright (C) 1997 and 1998 WIDE Project.
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  * 3. Neither the name of the project nor the names of its contributors
     16  *    may be used to endorse or promote products derived from this software
     17  *    without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
     20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
     23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     29  * SUCH DAMAGE.
     30  */
     31 
     32 #include <sys/param.h>
     33 #include <sys/types.h>
     34 #include <sys/socket.h>
     35 #include <sys/ioctl.h>
     36 #include <sys/time.h>
     37 #include <sys/wait.h>
     38 
     39 #include <stdio.h>
     40 #include <stdlib.h>
     41 #include <string.h>
     42 #include <syslog.h>
     43 #include <unistd.h>
     44 #include <errno.h>
     45 #include <fcntl.h>
     46 #include <signal.h>
     47 
     48 #include <netinet/in.h>
     49 #include <arpa/inet.h>
     50 #include <netdb.h>
     51 
     52 #include "faithd.h"
     53 
     54 static char tcpbuf[16*1024];
     55 	/* bigger than MSS and may be lesser than window size */
     56 static int tblen, tboff, oob_exists;
     57 static fd_set readfds, writefds, exceptfds;
     58 static char atmark_buf[2];
     59 static pid_t cpid = (pid_t)0;
     60 static pid_t ppid = (pid_t)0;
     61 static time_t child_lastactive = (time_t)0;
     62 static time_t parent_lastactive = (time_t)0;
     63 
     64 static void sig_ctimeout __P((int));
     65 static void sig_child __P((int));
     66 static void notify_inactive __P((void));
     67 static void notify_active __P((void));
     68 static void send_data __P((int, int, const char *, int));
     69 static void relay __P((int, int, const char *, int));
     70 
     71 /*
     72  * Inactivity timer:
     73  * - child side (ppid != 0) will send SIGUSR1 to parent every (FAITH_TIMEOUT/4)
     74  *   second if traffic is active.  if traffic is inactive, don't send SIGUSR1.
     75  * - parent side (ppid == 0) will check the last SIGUSR1 it have seen.
     76  */
     77 static void
     78 sig_ctimeout(int sig)
     79 {
     80 	/* parent side: record notification from the child */
     81 	if (dflag)
     82 		syslog(LOG_DEBUG, "activity timer from child");
     83 	child_lastactive = time(NULL);
     84 }
     85 
     86 /* parent will terminate if child dies. */
     87 static void
     88 sig_child(int sig)
     89 {
     90 	int status;
     91 	pid_t pid;
     92 
     93 	pid = wait3(&status, WNOHANG, (struct rusage *)0);
     94 	if (pid && status)
     95 		syslog(LOG_WARNING, "child %d exit status 0x%x", pid, status);
     96 	exit_failure("terminate connection due to child termination");
     97 }
     98 
     99 static void
    100 notify_inactive()
    101 {
    102 	time_t t;
    103 
    104 	/* only on parent side... */
    105 	if (ppid)
    106 		return;
    107 
    108 	/* parent side should check for timeout. */
    109 	t = time(NULL);
    110 	if (dflag) {
    111 		syslog(LOG_DEBUG, "parent side %sactive, child side %sactive",
    112 			(FAITH_TIMEOUT < t - parent_lastactive) ? "in" : "",
    113 			(FAITH_TIMEOUT < t - child_lastactive) ? "in" : "");
    114 	}
    115 
    116 	if (FAITH_TIMEOUT < t - child_lastactive
    117 	 && FAITH_TIMEOUT < t - parent_lastactive) {
    118 		/* both side timeouted */
    119 		signal(SIGCHLD, SIG_DFL);
    120 		kill(cpid, SIGTERM);
    121 		wait(NULL);
    122 		exit_failure("connection timeout");
    123 		/* NOTREACHED */
    124 	}
    125 }
    126 
    127 static void
    128 notify_active()
    129 {
    130 	if (ppid) {
    131 		/* child side: notify parent of active traffic */
    132 		time_t t;
    133 		t = time(NULL);
    134 		if (FAITH_TIMEOUT / 4 < t - child_lastactive) {
    135 			if (kill(ppid, SIGUSR1) < 0) {
    136 				exit_failure("terminate connection due to parent termination");
    137 				/* NOTREACHED */
    138 			}
    139 			child_lastactive = t;
    140 		}
    141 	} else {
    142 		/* parent side */
    143 		parent_lastactive = time(NULL);
    144 	}
    145 }
    146 
    147 static void
    148 send_data(int s_rcv, int s_snd, const char *service, int direction)
    149 {
    150 	int cc;
    151 
    152 	if (oob_exists) {
    153 		cc = send(s_snd, atmark_buf, 1, MSG_OOB);
    154 		if (cc == -1)
    155 			goto retry_or_err;
    156 		oob_exists = 0;
    157 		FD_SET(s_rcv, &exceptfds);
    158 	}
    159 
    160 	for (; tboff < tblen; tboff += cc) {
    161 		cc = write(s_snd, tcpbuf + tboff, tblen - tboff);
    162 		if (cc < 0)
    163 			goto retry_or_err;
    164 	}
    165 #ifdef DEBUG
    166 	if (tblen) {
    167 		if (tblen >= sizeof(tcpbuf))
    168 			tblen = sizeof(tcpbuf) - 1;
    169 	    	tcpbuf[tblen] = '\0';
    170 		syslog(LOG_DEBUG, "from %s (%dbytes): %s",
    171 		       direction == 1 ? "client" : "server", tblen, tcpbuf);
    172 	}
    173 #endif /* DEBUG */
    174 	tblen = 0; tboff = 0;
    175 	FD_CLR(s_snd, &writefds);
    176 	FD_SET(s_rcv, &readfds);
    177 	return;
    178     retry_or_err:
    179 	if (errno != EAGAIN)
    180 		exit_failure("writing relay data failed: %s", ERRSTR);
    181 	FD_SET(s_snd, &writefds);
    182 }
    183 
    184 static void
    185 relay(int s_rcv, int s_snd, const char *service, int direction)
    186 {
    187 	int atmark, error, maxfd;
    188 	struct timeval tv;
    189 	fd_set oreadfds, owritefds, oexceptfds;
    190 
    191 	FD_ZERO(&readfds);
    192 	FD_ZERO(&writefds);
    193 	FD_ZERO(&exceptfds);
    194 	fcntl(s_snd, F_SETFD, O_NONBLOCK);
    195 	oreadfds = readfds; owritefds = writefds; oexceptfds = exceptfds;
    196 	FD_SET(s_rcv, &readfds); FD_SET(s_rcv, &exceptfds);
    197 	oob_exists = 0;
    198 	maxfd = (s_rcv > s_snd) ? s_rcv : s_snd;
    199 
    200 	for (;;) {
    201 		tv.tv_sec = FAITH_TIMEOUT / 4;
    202 		tv.tv_usec = 0;
    203 		oreadfds = readfds;
    204 		owritefds = writefds;
    205 		oexceptfds = exceptfds;
    206 		error = select(maxfd + 1, &readfds, &writefds, &exceptfds, &tv);
    207 		if (error == -1) {
    208 			if (errno == EINTR)
    209 				continue;
    210 			exit_failure("select: %s", ERRSTR);
    211 		} else if (error == 0) {
    212 			readfds = oreadfds;
    213 			writefds = owritefds;
    214 			exceptfds = oexceptfds;
    215 			notify_inactive();
    216 			continue;
    217 		}
    218 
    219 		/* activity notification */
    220 		notify_active();
    221 
    222 		if (FD_ISSET(s_rcv, &exceptfds)) {
    223 			error = ioctl(s_rcv, SIOCATMARK, &atmark);
    224 			if (error != -1 && atmark == 1) {
    225 				int cc;
    226 			    oob_read_retry:
    227 				cc = read(s_rcv, atmark_buf, 1);
    228 				if (cc == 1) {
    229 					FD_CLR(s_rcv, &exceptfds);
    230 					FD_SET(s_snd, &writefds);
    231 					oob_exists = 1;
    232 				} else if (cc == -1) {
    233 					if (errno == EINTR)
    234 						goto oob_read_retry;
    235 					exit_failure("reading oob data failed"
    236 						     ": %s",
    237 						     ERRSTR);
    238 				}
    239 			}
    240 		}
    241 		if (FD_ISSET(s_rcv, &readfds)) {
    242 		    relaydata_read_retry:
    243 			tblen = read(s_rcv, tcpbuf, sizeof(tcpbuf));
    244 			tboff = 0;
    245 
    246 			switch (tblen) {
    247 			case -1:
    248 				if (errno == EINTR)
    249 					goto relaydata_read_retry;
    250 				exit_failure("reading relay data failed: %s",
    251 					     ERRSTR);
    252 				/* NOTREACHED */
    253 			case 0:
    254 				/* to close opposite-direction relay process */
    255 				shutdown(s_snd, 0);
    256 
    257 				close(s_rcv);
    258 				close(s_snd);
    259 				exit_success("terminating %s relay", service);
    260 				/* NOTREACHED */
    261 			default:
    262 				FD_CLR(s_rcv, &readfds);
    263 				FD_SET(s_snd, &writefds);
    264 				break;
    265 			}
    266 		}
    267 		if (FD_ISSET(s_snd, &writefds))
    268 			send_data(s_rcv, s_snd, service, direction);
    269 	}
    270 }
    271 
    272 void
    273 tcp_relay(int s_src, int s_dst, const char *service)
    274 {
    275 	syslog(LOG_INFO, "starting %s relay", service);
    276 
    277 	child_lastactive = parent_lastactive = time(NULL);
    278 
    279 	cpid = fork();
    280 	switch (cpid) {
    281 	case -1:
    282 		exit_failure("tcp_relay: can't fork grand child: %s", ERRSTR);
    283 		/* NOTREACHED */
    284 	case 0:
    285 		/* child process: relay going traffic */
    286 		ppid = getppid();
    287 		/* this is child so reopen log */
    288 		closelog();
    289 		openlog(logname, LOG_PID | LOG_NOWAIT, LOG_DAEMON);
    290 		relay(s_src, s_dst, service, 1);
    291 		/* NOTREACHED */
    292 	default:
    293 		/* parent process: relay coming traffic */
    294 		ppid = (pid_t)0;
    295 		signal(SIGUSR1, sig_ctimeout);
    296 		signal(SIGCHLD, sig_child);
    297 		relay(s_dst, s_src, service, 0);
    298 		/* NOTREACHED */
    299 	}
    300 }
    301