1/* $NetBSD: tcp.c,v 1.10 2010/11/26 18:58:43 christos 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 55static char tcpbuf[16*1024]; 56 /* bigger than MSS and may be lesser than window size */ 57static ssize_t tblen, tboff; 58static int oob_exists; 59static fd_set readfds, writefds, exceptfds; 60static char atmark_buf[2]; 61static pid_t cpid = (pid_t)0; 62static pid_t ppid = (pid_t)0; 63volatile time_t child_lastactive = (time_t)0; 64static time_t parent_lastactive = (time_t)0; 65 66static void sig_ctimeout(int); 67static void sig_child(int) __dead; 68static void notify_inactive(void); 69static void notify_active(void); 70static void send_data(int, int, const char *, int); 71static 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 */ 79static void 80/*ARGSUSED*/ 81sig_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. */ 90static void 91/*ARGSUSED*/ 92sig_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 104static void 105notify_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 132static void 133notify_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 152static void 153/*ARGSUSED*/ 154send_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 198static void 199relay(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; 243oob_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 297void 298tcp_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