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