1122679Sume/*	$KAME: tcp.c,v 1.13 2003/09/02 22:49:21 itojun Exp $	*/
262655Skris
356668Sshin/*
456668Sshin * Copyright (C) 1997 and 1998 WIDE Project.
556668Sshin * All rights reserved.
662655Skris *
756668Sshin * Redistribution and use in source and binary forms, with or without
856668Sshin * modification, are permitted provided that the following conditions
956668Sshin * are met:
1056668Sshin * 1. Redistributions of source code must retain the above copyright
1156668Sshin *    notice, this list of conditions and the following disclaimer.
1256668Sshin * 2. Redistributions in binary form must reproduce the above copyright
1356668Sshin *    notice, this list of conditions and the following disclaimer in the
1456668Sshin *    documentation and/or other materials provided with the distribution.
1556668Sshin * 3. Neither the name of the project nor the names of its contributors
1656668Sshin *    may be used to endorse or promote products derived from this software
1756668Sshin *    without specific prior written permission.
1862655Skris *
1956668Sshin * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
2056668Sshin * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2156668Sshin * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2256668Sshin * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
2356668Sshin * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2456668Sshin * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2556668Sshin * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2656668Sshin * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2756668Sshin * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2856668Sshin * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2956668Sshin * SUCH DAMAGE.
3056668Sshin */
3156668Sshin
32173298Scharnier#include <sys/cdefs.h>
33173298Scharnier__FBSDID("$FreeBSD: releng/10.3/usr.sbin/faithd/tcp.c 173412 2007-11-07 10:53:41Z kevlo $");
34173298Scharnier
3556668Sshin#include <sys/param.h>
3656668Sshin#include <sys/types.h>
3756668Sshin#include <sys/socket.h>
3856668Sshin#include <sys/ioctl.h>
3956668Sshin#include <sys/time.h>
4056668Sshin#include <sys/wait.h>
4156668Sshin
4256668Sshin#include <stdio.h>
4356668Sshin#include <stdlib.h>
4456668Sshin#include <string.h>
4556668Sshin#include <syslog.h>
4656668Sshin#include <unistd.h>
4756668Sshin#include <errno.h>
4856668Sshin#include <fcntl.h>
4956668Sshin#include <signal.h>
5056668Sshin
5156668Sshin#include <netinet/in.h>
5256668Sshin#include <arpa/inet.h>
5356668Sshin#include <netdb.h>
5456668Sshin
5556668Sshin#include "faithd.h"
5656668Sshin
5756668Sshinstatic char tcpbuf[16*1024];
5856668Sshin	/* bigger than MSS and may be lesser than window size */
5956668Sshinstatic int tblen, tboff, oob_exists;
6056668Sshinstatic fd_set readfds, writefds, exceptfds;
6156668Sshinstatic char atmark_buf[2];
6256668Sshinstatic pid_t cpid = (pid_t)0;
6356668Sshinstatic pid_t ppid = (pid_t)0;
6495023Ssuzvolatile time_t child_lastactive = (time_t)0;
6556668Sshinstatic time_t parent_lastactive = (time_t)0;
6656668Sshin
67173412Skevlostatic void sig_ctimeout(int);
68173412Skevlostatic void sig_child(int);
69173412Skevlostatic void notify_inactive(void);
70173412Skevlostatic void notify_active(void);
71173412Skevlostatic void send_data(int, int, const char *, int);
72173412Skevlostatic void relay(int, int, const char *, int);
7356668Sshin
7456668Sshin/*
7556668Sshin * Inactivity timer:
7656668Sshin * - child side (ppid != 0) will send SIGUSR1 to parent every (FAITH_TIMEOUT/4)
7756668Sshin *   second if traffic is active.  if traffic is inactive, don't send SIGUSR1.
7856668Sshin * - parent side (ppid == 0) will check the last SIGUSR1 it have seen.
7956668Sshin */
8056668Sshinstatic void
81173298Scharniersig_ctimeout(int sig __unused)
8256668Sshin{
8356668Sshin	/* parent side: record notification from the child */
8456668Sshin	if (dflag)
8556668Sshin		syslog(LOG_DEBUG, "activity timer from child");
8656668Sshin	child_lastactive = time(NULL);
8756668Sshin}
8856668Sshin
8956668Sshin/* parent will terminate if child dies. */
9056668Sshinstatic void
91173298Scharniersig_child(int sig __unused)
9256668Sshin{
9356668Sshin	int status;
9456668Sshin	pid_t pid;
9556668Sshin
9656668Sshin	pid = wait3(&status, WNOHANG, (struct rusage *)0);
97122679Sume	if (pid > 0 && WEXITSTATUS(status))
98122679Sume		syslog(LOG_WARNING, "child %ld exit status 0x%x",
99122679Sume		    (long)pid, status);
10078064Sume	exit_success("terminate connection due to child termination");
10156668Sshin}
10256668Sshin
10356668Sshinstatic void
10456668Sshinnotify_inactive()
10556668Sshin{
10656668Sshin	time_t t;
10756668Sshin
10856668Sshin	/* only on parent side... */
10956668Sshin	if (ppid)
11056668Sshin		return;
11156668Sshin
11256668Sshin	/* parent side should check for timeout. */
11356668Sshin	t = time(NULL);
11456668Sshin	if (dflag) {
11556668Sshin		syslog(LOG_DEBUG, "parent side %sactive, child side %sactive",
11656668Sshin			(FAITH_TIMEOUT < t - parent_lastactive) ? "in" : "",
11756668Sshin			(FAITH_TIMEOUT < t - child_lastactive) ? "in" : "");
11856668Sshin	}
11956668Sshin
12056668Sshin	if (FAITH_TIMEOUT < t - child_lastactive
12156668Sshin	 && FAITH_TIMEOUT < t - parent_lastactive) {
12256668Sshin		/* both side timeouted */
12356668Sshin		signal(SIGCHLD, SIG_DFL);
12456668Sshin		kill(cpid, SIGTERM);
12556668Sshin		wait(NULL);
12656668Sshin		exit_failure("connection timeout");
12756668Sshin		/* NOTREACHED */
12856668Sshin	}
12956668Sshin}
13056668Sshin
13156668Sshinstatic void
13256668Sshinnotify_active()
13356668Sshin{
13456668Sshin	if (ppid) {
13556668Sshin		/* child side: notify parent of active traffic */
13656668Sshin		time_t t;
13756668Sshin		t = time(NULL);
13856668Sshin		if (FAITH_TIMEOUT / 4 < t - child_lastactive) {
13956668Sshin			if (kill(ppid, SIGUSR1) < 0) {
14056668Sshin				exit_failure("terminate connection due to parent termination");
14156668Sshin				/* NOTREACHED */
14256668Sshin			}
14356668Sshin			child_lastactive = t;
14456668Sshin		}
14556668Sshin	} else {
14656668Sshin		/* parent side */
14756668Sshin		parent_lastactive = time(NULL);
14856668Sshin	}
14956668Sshin}
15056668Sshin
15156668Sshinstatic void
152173298Scharniersend_data(int s_rcv, int s_snd, const char *service __unused, int direction)
15356668Sshin{
15456668Sshin	int cc;
15556668Sshin
15656668Sshin	if (oob_exists) {
15756668Sshin		cc = send(s_snd, atmark_buf, 1, MSG_OOB);
15856668Sshin		if (cc == -1)
15956668Sshin			goto retry_or_err;
16056668Sshin		oob_exists = 0;
161122679Sume		if (s_rcv >= FD_SETSIZE)
162122679Sume			exit_failure("descriptor too big");
16356668Sshin		FD_SET(s_rcv, &exceptfds);
16456668Sshin	}
16556668Sshin
16656668Sshin	for (; tboff < tblen; tboff += cc) {
16756668Sshin		cc = write(s_snd, tcpbuf + tboff, tblen - tboff);
16856668Sshin		if (cc < 0)
16956668Sshin			goto retry_or_err;
17056668Sshin	}
17156668Sshin#ifdef DEBUG
17256668Sshin	if (tblen) {
17356668Sshin		if (tblen >= sizeof(tcpbuf))
17456668Sshin			tblen = sizeof(tcpbuf) - 1;
17556668Sshin	    	tcpbuf[tblen] = '\0';
17656668Sshin		syslog(LOG_DEBUG, "from %s (%dbytes): %s",
17756668Sshin		       direction == 1 ? "client" : "server", tblen, tcpbuf);
17856668Sshin	}
17956668Sshin#endif /* DEBUG */
18056668Sshin	tblen = 0; tboff = 0;
181122679Sume	if (s_snd >= FD_SETSIZE)
182122679Sume		exit_failure("descriptor too big");
18356668Sshin	FD_CLR(s_snd, &writefds);
184122679Sume	if (s_rcv >= FD_SETSIZE)
185122679Sume		exit_failure("descriptor too big");
18656668Sshin	FD_SET(s_rcv, &readfds);
18756668Sshin	return;
18856668Sshin    retry_or_err:
18956668Sshin	if (errno != EAGAIN)
19095023Ssuz		exit_failure("writing relay data failed: %s", strerror(errno));
191122679Sume	if (s_snd >= FD_SETSIZE)
192122679Sume		exit_failure("descriptor too big");
19356668Sshin	FD_SET(s_snd, &writefds);
19456668Sshin}
19556668Sshin
19656668Sshinstatic void
19756668Sshinrelay(int s_rcv, int s_snd, const char *service, int direction)
19856668Sshin{
19956668Sshin	int atmark, error, maxfd;
20056668Sshin	struct timeval tv;
20156668Sshin	fd_set oreadfds, owritefds, oexceptfds;
20256668Sshin
20356668Sshin	FD_ZERO(&readfds);
20456668Sshin	FD_ZERO(&writefds);
20556668Sshin	FD_ZERO(&exceptfds);
20656668Sshin	fcntl(s_snd, F_SETFD, O_NONBLOCK);
20756668Sshin	oreadfds = readfds; owritefds = writefds; oexceptfds = exceptfds;
208122679Sume	if (s_rcv >= FD_SETSIZE)
209122679Sume		exit_failure("descriptor too big");
21078064Sume	FD_SET(s_rcv, &readfds);
21178064Sume	FD_SET(s_rcv, &exceptfds);
21256668Sshin	oob_exists = 0;
21356668Sshin	maxfd = (s_rcv > s_snd) ? s_rcv : s_snd;
21456668Sshin
21556668Sshin	for (;;) {
21656668Sshin		tv.tv_sec = FAITH_TIMEOUT / 4;
21756668Sshin		tv.tv_usec = 0;
21856668Sshin		oreadfds = readfds;
21956668Sshin		owritefds = writefds;
22056668Sshin		oexceptfds = exceptfds;
22156668Sshin		error = select(maxfd + 1, &readfds, &writefds, &exceptfds, &tv);
22256668Sshin		if (error == -1) {
22356668Sshin			if (errno == EINTR)
22456668Sshin				continue;
22595023Ssuz			exit_failure("select: %s", strerror(errno));
22656668Sshin		} else if (error == 0) {
22756668Sshin			readfds = oreadfds;
22856668Sshin			writefds = owritefds;
22956668Sshin			exceptfds = oexceptfds;
23056668Sshin			notify_inactive();
23156668Sshin			continue;
23256668Sshin		}
23356668Sshin
23456668Sshin		/* activity notification */
23556668Sshin		notify_active();
23656668Sshin
23756668Sshin		if (FD_ISSET(s_rcv, &exceptfds)) {
23856668Sshin			error = ioctl(s_rcv, SIOCATMARK, &atmark);
23956668Sshin			if (error != -1 && atmark == 1) {
24056668Sshin				int cc;
24156668Sshin			    oob_read_retry:
24256668Sshin				cc = read(s_rcv, atmark_buf, 1);
24356668Sshin				if (cc == 1) {
244122679Sume					if (s_rcv >= FD_SETSIZE)
245122679Sume						exit_failure("descriptor too big");
24656668Sshin					FD_CLR(s_rcv, &exceptfds);
247122679Sume					if (s_snd >= FD_SETSIZE)
248122679Sume						exit_failure("descriptor too big");
24956668Sshin					FD_SET(s_snd, &writefds);
25056668Sshin					oob_exists = 1;
25156668Sshin				} else if (cc == -1) {
25256668Sshin					if (errno == EINTR)
25356668Sshin						goto oob_read_retry;
25456668Sshin					exit_failure("reading oob data failed"
25556668Sshin						     ": %s",
25695023Ssuz						     strerror(errno));
25756668Sshin				}
25856668Sshin			}
25956668Sshin		}
26056668Sshin		if (FD_ISSET(s_rcv, &readfds)) {
26156668Sshin		    relaydata_read_retry:
26256668Sshin			tblen = read(s_rcv, tcpbuf, sizeof(tcpbuf));
26356668Sshin			tboff = 0;
26456668Sshin
26556668Sshin			switch (tblen) {
26656668Sshin			case -1:
26756668Sshin				if (errno == EINTR)
26856668Sshin					goto relaydata_read_retry;
26956668Sshin				exit_failure("reading relay data failed: %s",
27095023Ssuz					     strerror(errno));
27156668Sshin				/* NOTREACHED */
27256668Sshin			case 0:
27356668Sshin				/* to close opposite-direction relay process */
27456668Sshin				shutdown(s_snd, 0);
27556668Sshin
27656668Sshin				close(s_rcv);
27756668Sshin				close(s_snd);
27856668Sshin				exit_success("terminating %s relay", service);
27956668Sshin				/* NOTREACHED */
28056668Sshin			default:
281122679Sume				if (s_rcv >= FD_SETSIZE)
282122679Sume					exit_failure("descriptor too big");
28356668Sshin				FD_CLR(s_rcv, &readfds);
284122679Sume				if (s_snd >= FD_SETSIZE)
285122679Sume					exit_failure("descriptor too big");
28656668Sshin				FD_SET(s_snd, &writefds);
28756668Sshin				break;
28856668Sshin			}
28956668Sshin		}
29056668Sshin		if (FD_ISSET(s_snd, &writefds))
29156668Sshin			send_data(s_rcv, s_snd, service, direction);
29256668Sshin	}
29356668Sshin}
29456668Sshin
29556668Sshinvoid
29656668Sshintcp_relay(int s_src, int s_dst, const char *service)
29756668Sshin{
29856668Sshin	syslog(LOG_INFO, "starting %s relay", service);
29956668Sshin
30056668Sshin	child_lastactive = parent_lastactive = time(NULL);
30156668Sshin
30256668Sshin	cpid = fork();
30356668Sshin	switch (cpid) {
30456668Sshin	case -1:
30595023Ssuz		exit_failure("tcp_relay: can't fork grand child: %s",
30695023Ssuz		    strerror(errno));
30756668Sshin		/* NOTREACHED */
30856668Sshin	case 0:
30956668Sshin		/* child process: relay going traffic */
31056668Sshin		ppid = getppid();
31156668Sshin		/* this is child so reopen log */
31256668Sshin		closelog();
31356668Sshin		openlog(logname, LOG_PID | LOG_NOWAIT, LOG_DAEMON);
31456668Sshin		relay(s_src, s_dst, service, 1);
31556668Sshin		/* NOTREACHED */
31656668Sshin	default:
31756668Sshin		/* parent process: relay coming traffic */
31856668Sshin		ppid = (pid_t)0;
31956668Sshin		signal(SIGUSR1, sig_ctimeout);
32056668Sshin		signal(SIGCHLD, sig_child);
32156668Sshin		relay(s_dst, s_src, service, 0);
32256668Sshin		/* NOTREACHED */
32356668Sshin	}
32456668Sshin}
325