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