1 /* $NetBSD: tcp.c,v 1.11 2011/08/30 21:14:06 joerg 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
55 static char tcpbuf[16*1024];
56 /* bigger than MSS and may be lesser than window size */
57 static ssize_t tblen, tboff;
58 static int oob_exists;
59 static fd_set readfds, writefds, exceptfds;
60 static char atmark_buf[2];
61 static pid_t cpid = (pid_t)0;
62 static pid_t ppid = (pid_t)0;
63 volatile time_t child_lastactive = (time_t)0;
64 static time_t parent_lastactive = (time_t)0;
65
66 static void sig_ctimeout(int);
67 static void sig_child(int) __dead;
68 static void notify_inactive(void);
69 static void notify_active(void);
70 static void send_data(int, int, const char *, int);
71 static 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 */
79 static void
80 /*ARGSUSED*/
sig_ctimeout(int sig)81 sig_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. */
90 static void
91 /*ARGSUSED*/
sig_child(int sig)92 sig_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
104 static void
notify_inactive()105 notify_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
132 static void
notify_active()133 notify_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
152 static void
153 /*ARGSUSED*/
send_data(int s_rcv,int s_snd,const char * service,int direction)154 send_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
198 static void
relay(int s_rcv,int s_snd,const char * service,int direction)199 relay(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;
243 oob_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
297 void
tcp_relay(int s_src,int s_dst,const char * service)298 tcp_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