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