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