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