xref: /netbsd-src/usr.sbin/faithd/tcp.c (revision 6ede639fd64e6aa39ff4e1f157d4577ada8110de)
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