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