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