1/*	$KAME: tcp.c,v 1.13 2003/09/02 22:49:21 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/cdefs.h>
33__FBSDID("$FreeBSD$");
34
35#include <sys/param.h>
36#include <sys/types.h>
37#include <sys/socket.h>
38#include <sys/ioctl.h>
39#include <sys/time.h>
40#include <sys/wait.h>
41
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <syslog.h>
46#include <unistd.h>
47#include <errno.h>
48#include <fcntl.h>
49#include <signal.h>
50
51#include <netinet/in.h>
52#include <arpa/inet.h>
53#include <netdb.h>
54
55#include "faithd.h"
56
57static char tcpbuf[16*1024];
58	/* bigger than MSS and may be lesser than window size */
59static int tblen, tboff, oob_exists;
60static fd_set readfds, writefds, exceptfds;
61static char atmark_buf[2];
62static pid_t cpid = (pid_t)0;
63static pid_t ppid = (pid_t)0;
64volatile time_t child_lastactive = (time_t)0;
65static time_t parent_lastactive = (time_t)0;
66
67static void sig_ctimeout(int);
68static void sig_child(int);
69static void notify_inactive(void);
70static void notify_active(void);
71static void send_data(int, int, const char *, int);
72static void relay(int, int, const char *, int);
73
74/*
75 * Inactivity timer:
76 * - child side (ppid != 0) will send SIGUSR1 to parent every (FAITH_TIMEOUT/4)
77 *   second if traffic is active.  if traffic is inactive, don't send SIGUSR1.
78 * - parent side (ppid == 0) will check the last SIGUSR1 it have seen.
79 */
80static void
81sig_ctimeout(int sig __unused)
82{
83	/* parent side: record notification from the child */
84	if (dflag)
85		syslog(LOG_DEBUG, "activity timer from child");
86	child_lastactive = time(NULL);
87}
88
89/* parent will terminate if child dies. */
90static void
91sig_child(int sig __unused)
92{
93	int status;
94	pid_t pid;
95
96	pid = wait3(&status, WNOHANG, (struct rusage *)0);
97	if (pid > 0 && WEXITSTATUS(status))
98		syslog(LOG_WARNING, "child %ld exit status 0x%x",
99		    (long)pid, status);
100	exit_success("terminate connection due to child termination");
101}
102
103static void
104notify_inactive()
105{
106	time_t t;
107
108	/* only on parent side... */
109	if (ppid)
110		return;
111
112	/* parent side should check for timeout. */
113	t = time(NULL);
114	if (dflag) {
115		syslog(LOG_DEBUG, "parent side %sactive, child side %sactive",
116			(FAITH_TIMEOUT < t - parent_lastactive) ? "in" : "",
117			(FAITH_TIMEOUT < t - child_lastactive) ? "in" : "");
118	}
119
120	if (FAITH_TIMEOUT < t - child_lastactive
121	 && FAITH_TIMEOUT < t - parent_lastactive) {
122		/* both side timeouted */
123		signal(SIGCHLD, SIG_DFL);
124		kill(cpid, SIGTERM);
125		wait(NULL);
126		exit_failure("connection timeout");
127		/* NOTREACHED */
128	}
129}
130
131static void
132notify_active()
133{
134	if (ppid) {
135		/* child side: notify parent of active traffic */
136		time_t t;
137		t = time(NULL);
138		if (FAITH_TIMEOUT / 4 < t - child_lastactive) {
139			if (kill(ppid, SIGUSR1) < 0) {
140				exit_failure("terminate connection due to parent termination");
141				/* NOTREACHED */
142			}
143			child_lastactive = t;
144		}
145	} else {
146		/* parent side */
147		parent_lastactive = time(NULL);
148	}
149}
150
151static void
152send_data(int s_rcv, int s_snd, const char *service __unused, int direction)
153{
154	int cc;
155
156	if (oob_exists) {
157		cc = send(s_snd, atmark_buf, 1, MSG_OOB);
158		if (cc == -1)
159			goto retry_or_err;
160		oob_exists = 0;
161		if (s_rcv >= FD_SETSIZE)
162			exit_failure("descriptor too big");
163		FD_SET(s_rcv, &exceptfds);
164	}
165
166	for (; tboff < tblen; tboff += cc) {
167		cc = write(s_snd, tcpbuf + tboff, tblen - tboff);
168		if (cc < 0)
169			goto retry_or_err;
170	}
171#ifdef DEBUG
172	if (tblen) {
173		if (tblen >= sizeof(tcpbuf))
174			tblen = sizeof(tcpbuf) - 1;
175	    	tcpbuf[tblen] = '\0';
176		syslog(LOG_DEBUG, "from %s (%dbytes): %s",
177		       direction == 1 ? "client" : "server", tblen, tcpbuf);
178	}
179#endif /* DEBUG */
180	tblen = 0; tboff = 0;
181	if (s_snd >= FD_SETSIZE)
182		exit_failure("descriptor too big");
183	FD_CLR(s_snd, &writefds);
184	if (s_rcv >= FD_SETSIZE)
185		exit_failure("descriptor too big");
186	FD_SET(s_rcv, &readfds);
187	return;
188    retry_or_err:
189	if (errno != EAGAIN)
190		exit_failure("writing relay data failed: %s", strerror(errno));
191	if (s_snd >= FD_SETSIZE)
192		exit_failure("descriptor too big");
193	FD_SET(s_snd, &writefds);
194}
195
196static void
197relay(int s_rcv, int s_snd, const char *service, int direction)
198{
199	int atmark, error, maxfd;
200	struct timeval tv;
201	fd_set oreadfds, owritefds, oexceptfds;
202
203	FD_ZERO(&readfds);
204	FD_ZERO(&writefds);
205	FD_ZERO(&exceptfds);
206	fcntl(s_snd, F_SETFD, O_NONBLOCK);
207	oreadfds = readfds; owritefds = writefds; oexceptfds = exceptfds;
208	if (s_rcv >= FD_SETSIZE)
209		exit_failure("descriptor too big");
210	FD_SET(s_rcv, &readfds);
211	FD_SET(s_rcv, &exceptfds);
212	oob_exists = 0;
213	maxfd = (s_rcv > s_snd) ? s_rcv : s_snd;
214
215	for (;;) {
216		tv.tv_sec = FAITH_TIMEOUT / 4;
217		tv.tv_usec = 0;
218		oreadfds = readfds;
219		owritefds = writefds;
220		oexceptfds = exceptfds;
221		error = select(maxfd + 1, &readfds, &writefds, &exceptfds, &tv);
222		if (error == -1) {
223			if (errno == EINTR)
224				continue;
225			exit_failure("select: %s", strerror(errno));
226		} else if (error == 0) {
227			readfds = oreadfds;
228			writefds = owritefds;
229			exceptfds = oexceptfds;
230			notify_inactive();
231			continue;
232		}
233
234		/* activity notification */
235		notify_active();
236
237		if (FD_ISSET(s_rcv, &exceptfds)) {
238			error = ioctl(s_rcv, SIOCATMARK, &atmark);
239			if (error != -1 && atmark == 1) {
240				int cc;
241			    oob_read_retry:
242				cc = read(s_rcv, atmark_buf, 1);
243				if (cc == 1) {
244					if (s_rcv >= FD_SETSIZE)
245						exit_failure("descriptor too big");
246					FD_CLR(s_rcv, &exceptfds);
247					if (s_snd >= FD_SETSIZE)
248						exit_failure("descriptor too big");
249					FD_SET(s_snd, &writefds);
250					oob_exists = 1;
251				} else if (cc == -1) {
252					if (errno == EINTR)
253						goto oob_read_retry;
254					exit_failure("reading oob data failed"
255						     ": %s",
256						     strerror(errno));
257				}
258			}
259		}
260		if (FD_ISSET(s_rcv, &readfds)) {
261		    relaydata_read_retry:
262			tblen = read(s_rcv, tcpbuf, sizeof(tcpbuf));
263			tboff = 0;
264
265			switch (tblen) {
266			case -1:
267				if (errno == EINTR)
268					goto relaydata_read_retry;
269				exit_failure("reading relay data failed: %s",
270					     strerror(errno));
271				/* NOTREACHED */
272			case 0:
273				/* to close opposite-direction relay process */
274				shutdown(s_snd, 0);
275
276				close(s_rcv);
277				close(s_snd);
278				exit_success("terminating %s relay", service);
279				/* NOTREACHED */
280			default:
281				if (s_rcv >= FD_SETSIZE)
282					exit_failure("descriptor too big");
283				FD_CLR(s_rcv, &readfds);
284				if (s_snd >= FD_SETSIZE)
285					exit_failure("descriptor too big");
286				FD_SET(s_snd, &writefds);
287				break;
288			}
289		}
290		if (FD_ISSET(s_snd, &writefds))
291			send_data(s_rcv, s_snd, service, direction);
292	}
293}
294
295void
296tcp_relay(int s_src, int s_dst, const char *service)
297{
298	syslog(LOG_INFO, "starting %s relay", service);
299
300	child_lastactive = parent_lastactive = time(NULL);
301
302	cpid = fork();
303	switch (cpid) {
304	case -1:
305		exit_failure("tcp_relay: can't fork grand child: %s",
306		    strerror(errno));
307		/* NOTREACHED */
308	case 0:
309		/* child process: relay going traffic */
310		ppid = getppid();
311		/* this is child so reopen log */
312		closelog();
313		openlog(logname, LOG_PID | LOG_NOWAIT, LOG_DAEMON);
314		relay(s_src, s_dst, service, 1);
315		/* NOTREACHED */
316	default:
317		/* parent process: relay coming traffic */
318		ppid = (pid_t)0;
319		signal(SIGUSR1, sig_ctimeout);
320		signal(SIGCHLD, sig_child);
321		relay(s_dst, s_src, service, 0);
322		/* NOTREACHED */
323	}
324}
325