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