1122679Sume/* $KAME: tcp.c,v 1.13 2003/09/02 22:49:21 itojun Exp $ */ 262655Skris 356668Sshin/* 456668Sshin * Copyright (C) 1997 and 1998 WIDE Project. 556668Sshin * All rights reserved. 662655Skris * 756668Sshin * Redistribution and use in source and binary forms, with or without 856668Sshin * modification, are permitted provided that the following conditions 956668Sshin * are met: 1056668Sshin * 1. Redistributions of source code must retain the above copyright 1156668Sshin * notice, this list of conditions and the following disclaimer. 1256668Sshin * 2. Redistributions in binary form must reproduce the above copyright 1356668Sshin * notice, this list of conditions and the following disclaimer in the 1456668Sshin * documentation and/or other materials provided with the distribution. 1556668Sshin * 3. Neither the name of the project nor the names of its contributors 1656668Sshin * may be used to endorse or promote products derived from this software 1756668Sshin * without specific prior written permission. 1862655Skris * 1956668Sshin * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 2056668Sshin * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2156668Sshin * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2256668Sshin * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 2356668Sshin * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2456668Sshin * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2556668Sshin * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2656668Sshin * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2756668Sshin * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2856668Sshin * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2956668Sshin * SUCH DAMAGE. 3056668Sshin */ 3156668Sshin 32173298Scharnier#include <sys/cdefs.h> 33173298Scharnier__FBSDID("$FreeBSD: releng/10.3/usr.sbin/faithd/tcp.c 173412 2007-11-07 10:53:41Z kevlo $"); 34173298Scharnier 3556668Sshin#include <sys/param.h> 3656668Sshin#include <sys/types.h> 3756668Sshin#include <sys/socket.h> 3856668Sshin#include <sys/ioctl.h> 3956668Sshin#include <sys/time.h> 4056668Sshin#include <sys/wait.h> 4156668Sshin 4256668Sshin#include <stdio.h> 4356668Sshin#include <stdlib.h> 4456668Sshin#include <string.h> 4556668Sshin#include <syslog.h> 4656668Sshin#include <unistd.h> 4756668Sshin#include <errno.h> 4856668Sshin#include <fcntl.h> 4956668Sshin#include <signal.h> 5056668Sshin 5156668Sshin#include <netinet/in.h> 5256668Sshin#include <arpa/inet.h> 5356668Sshin#include <netdb.h> 5456668Sshin 5556668Sshin#include "faithd.h" 5656668Sshin 5756668Sshinstatic char tcpbuf[16*1024]; 5856668Sshin /* bigger than MSS and may be lesser than window size */ 5956668Sshinstatic int tblen, tboff, oob_exists; 6056668Sshinstatic fd_set readfds, writefds, exceptfds; 6156668Sshinstatic char atmark_buf[2]; 6256668Sshinstatic pid_t cpid = (pid_t)0; 6356668Sshinstatic pid_t ppid = (pid_t)0; 6495023Ssuzvolatile time_t child_lastactive = (time_t)0; 6556668Sshinstatic time_t parent_lastactive = (time_t)0; 6656668Sshin 67173412Skevlostatic void sig_ctimeout(int); 68173412Skevlostatic void sig_child(int); 69173412Skevlostatic void notify_inactive(void); 70173412Skevlostatic void notify_active(void); 71173412Skevlostatic void send_data(int, int, const char *, int); 72173412Skevlostatic void relay(int, int, const char *, int); 7356668Sshin 7456668Sshin/* 7556668Sshin * Inactivity timer: 7656668Sshin * - child side (ppid != 0) will send SIGUSR1 to parent every (FAITH_TIMEOUT/4) 7756668Sshin * second if traffic is active. if traffic is inactive, don't send SIGUSR1. 7856668Sshin * - parent side (ppid == 0) will check the last SIGUSR1 it have seen. 7956668Sshin */ 8056668Sshinstatic void 81173298Scharniersig_ctimeout(int sig __unused) 8256668Sshin{ 8356668Sshin /* parent side: record notification from the child */ 8456668Sshin if (dflag) 8556668Sshin syslog(LOG_DEBUG, "activity timer from child"); 8656668Sshin child_lastactive = time(NULL); 8756668Sshin} 8856668Sshin 8956668Sshin/* parent will terminate if child dies. */ 9056668Sshinstatic void 91173298Scharniersig_child(int sig __unused) 9256668Sshin{ 9356668Sshin int status; 9456668Sshin pid_t pid; 9556668Sshin 9656668Sshin pid = wait3(&status, WNOHANG, (struct rusage *)0); 97122679Sume if (pid > 0 && WEXITSTATUS(status)) 98122679Sume syslog(LOG_WARNING, "child %ld exit status 0x%x", 99122679Sume (long)pid, status); 10078064Sume exit_success("terminate connection due to child termination"); 10156668Sshin} 10256668Sshin 10356668Sshinstatic void 10456668Sshinnotify_inactive() 10556668Sshin{ 10656668Sshin time_t t; 10756668Sshin 10856668Sshin /* only on parent side... */ 10956668Sshin if (ppid) 11056668Sshin return; 11156668Sshin 11256668Sshin /* parent side should check for timeout. */ 11356668Sshin t = time(NULL); 11456668Sshin if (dflag) { 11556668Sshin syslog(LOG_DEBUG, "parent side %sactive, child side %sactive", 11656668Sshin (FAITH_TIMEOUT < t - parent_lastactive) ? "in" : "", 11756668Sshin (FAITH_TIMEOUT < t - child_lastactive) ? "in" : ""); 11856668Sshin } 11956668Sshin 12056668Sshin if (FAITH_TIMEOUT < t - child_lastactive 12156668Sshin && FAITH_TIMEOUT < t - parent_lastactive) { 12256668Sshin /* both side timeouted */ 12356668Sshin signal(SIGCHLD, SIG_DFL); 12456668Sshin kill(cpid, SIGTERM); 12556668Sshin wait(NULL); 12656668Sshin exit_failure("connection timeout"); 12756668Sshin /* NOTREACHED */ 12856668Sshin } 12956668Sshin} 13056668Sshin 13156668Sshinstatic void 13256668Sshinnotify_active() 13356668Sshin{ 13456668Sshin if (ppid) { 13556668Sshin /* child side: notify parent of active traffic */ 13656668Sshin time_t t; 13756668Sshin t = time(NULL); 13856668Sshin if (FAITH_TIMEOUT / 4 < t - child_lastactive) { 13956668Sshin if (kill(ppid, SIGUSR1) < 0) { 14056668Sshin exit_failure("terminate connection due to parent termination"); 14156668Sshin /* NOTREACHED */ 14256668Sshin } 14356668Sshin child_lastactive = t; 14456668Sshin } 14556668Sshin } else { 14656668Sshin /* parent side */ 14756668Sshin parent_lastactive = time(NULL); 14856668Sshin } 14956668Sshin} 15056668Sshin 15156668Sshinstatic void 152173298Scharniersend_data(int s_rcv, int s_snd, const char *service __unused, int direction) 15356668Sshin{ 15456668Sshin int cc; 15556668Sshin 15656668Sshin if (oob_exists) { 15756668Sshin cc = send(s_snd, atmark_buf, 1, MSG_OOB); 15856668Sshin if (cc == -1) 15956668Sshin goto retry_or_err; 16056668Sshin oob_exists = 0; 161122679Sume if (s_rcv >= FD_SETSIZE) 162122679Sume exit_failure("descriptor too big"); 16356668Sshin FD_SET(s_rcv, &exceptfds); 16456668Sshin } 16556668Sshin 16656668Sshin for (; tboff < tblen; tboff += cc) { 16756668Sshin cc = write(s_snd, tcpbuf + tboff, tblen - tboff); 16856668Sshin if (cc < 0) 16956668Sshin goto retry_or_err; 17056668Sshin } 17156668Sshin#ifdef DEBUG 17256668Sshin if (tblen) { 17356668Sshin if (tblen >= sizeof(tcpbuf)) 17456668Sshin tblen = sizeof(tcpbuf) - 1; 17556668Sshin tcpbuf[tblen] = '\0'; 17656668Sshin syslog(LOG_DEBUG, "from %s (%dbytes): %s", 17756668Sshin direction == 1 ? "client" : "server", tblen, tcpbuf); 17856668Sshin } 17956668Sshin#endif /* DEBUG */ 18056668Sshin tblen = 0; tboff = 0; 181122679Sume if (s_snd >= FD_SETSIZE) 182122679Sume exit_failure("descriptor too big"); 18356668Sshin FD_CLR(s_snd, &writefds); 184122679Sume if (s_rcv >= FD_SETSIZE) 185122679Sume exit_failure("descriptor too big"); 18656668Sshin FD_SET(s_rcv, &readfds); 18756668Sshin return; 18856668Sshin retry_or_err: 18956668Sshin if (errno != EAGAIN) 19095023Ssuz exit_failure("writing relay data failed: %s", strerror(errno)); 191122679Sume if (s_snd >= FD_SETSIZE) 192122679Sume exit_failure("descriptor too big"); 19356668Sshin FD_SET(s_snd, &writefds); 19456668Sshin} 19556668Sshin 19656668Sshinstatic void 19756668Sshinrelay(int s_rcv, int s_snd, const char *service, int direction) 19856668Sshin{ 19956668Sshin int atmark, error, maxfd; 20056668Sshin struct timeval tv; 20156668Sshin fd_set oreadfds, owritefds, oexceptfds; 20256668Sshin 20356668Sshin FD_ZERO(&readfds); 20456668Sshin FD_ZERO(&writefds); 20556668Sshin FD_ZERO(&exceptfds); 20656668Sshin fcntl(s_snd, F_SETFD, O_NONBLOCK); 20756668Sshin oreadfds = readfds; owritefds = writefds; oexceptfds = exceptfds; 208122679Sume if (s_rcv >= FD_SETSIZE) 209122679Sume exit_failure("descriptor too big"); 21078064Sume FD_SET(s_rcv, &readfds); 21178064Sume FD_SET(s_rcv, &exceptfds); 21256668Sshin oob_exists = 0; 21356668Sshin maxfd = (s_rcv > s_snd) ? s_rcv : s_snd; 21456668Sshin 21556668Sshin for (;;) { 21656668Sshin tv.tv_sec = FAITH_TIMEOUT / 4; 21756668Sshin tv.tv_usec = 0; 21856668Sshin oreadfds = readfds; 21956668Sshin owritefds = writefds; 22056668Sshin oexceptfds = exceptfds; 22156668Sshin error = select(maxfd + 1, &readfds, &writefds, &exceptfds, &tv); 22256668Sshin if (error == -1) { 22356668Sshin if (errno == EINTR) 22456668Sshin continue; 22595023Ssuz exit_failure("select: %s", strerror(errno)); 22656668Sshin } else if (error == 0) { 22756668Sshin readfds = oreadfds; 22856668Sshin writefds = owritefds; 22956668Sshin exceptfds = oexceptfds; 23056668Sshin notify_inactive(); 23156668Sshin continue; 23256668Sshin } 23356668Sshin 23456668Sshin /* activity notification */ 23556668Sshin notify_active(); 23656668Sshin 23756668Sshin if (FD_ISSET(s_rcv, &exceptfds)) { 23856668Sshin error = ioctl(s_rcv, SIOCATMARK, &atmark); 23956668Sshin if (error != -1 && atmark == 1) { 24056668Sshin int cc; 24156668Sshin oob_read_retry: 24256668Sshin cc = read(s_rcv, atmark_buf, 1); 24356668Sshin if (cc == 1) { 244122679Sume if (s_rcv >= FD_SETSIZE) 245122679Sume exit_failure("descriptor too big"); 24656668Sshin FD_CLR(s_rcv, &exceptfds); 247122679Sume if (s_snd >= FD_SETSIZE) 248122679Sume exit_failure("descriptor too big"); 24956668Sshin FD_SET(s_snd, &writefds); 25056668Sshin oob_exists = 1; 25156668Sshin } else if (cc == -1) { 25256668Sshin if (errno == EINTR) 25356668Sshin goto oob_read_retry; 25456668Sshin exit_failure("reading oob data failed" 25556668Sshin ": %s", 25695023Ssuz strerror(errno)); 25756668Sshin } 25856668Sshin } 25956668Sshin } 26056668Sshin if (FD_ISSET(s_rcv, &readfds)) { 26156668Sshin relaydata_read_retry: 26256668Sshin tblen = read(s_rcv, tcpbuf, sizeof(tcpbuf)); 26356668Sshin tboff = 0; 26456668Sshin 26556668Sshin switch (tblen) { 26656668Sshin case -1: 26756668Sshin if (errno == EINTR) 26856668Sshin goto relaydata_read_retry; 26956668Sshin exit_failure("reading relay data failed: %s", 27095023Ssuz strerror(errno)); 27156668Sshin /* NOTREACHED */ 27256668Sshin case 0: 27356668Sshin /* to close opposite-direction relay process */ 27456668Sshin shutdown(s_snd, 0); 27556668Sshin 27656668Sshin close(s_rcv); 27756668Sshin close(s_snd); 27856668Sshin exit_success("terminating %s relay", service); 27956668Sshin /* NOTREACHED */ 28056668Sshin default: 281122679Sume if (s_rcv >= FD_SETSIZE) 282122679Sume exit_failure("descriptor too big"); 28356668Sshin FD_CLR(s_rcv, &readfds); 284122679Sume if (s_snd >= FD_SETSIZE) 285122679Sume exit_failure("descriptor too big"); 28656668Sshin FD_SET(s_snd, &writefds); 28756668Sshin break; 28856668Sshin } 28956668Sshin } 29056668Sshin if (FD_ISSET(s_snd, &writefds)) 29156668Sshin send_data(s_rcv, s_snd, service, direction); 29256668Sshin } 29356668Sshin} 29456668Sshin 29556668Sshinvoid 29656668Sshintcp_relay(int s_src, int s_dst, const char *service) 29756668Sshin{ 29856668Sshin syslog(LOG_INFO, "starting %s relay", service); 29956668Sshin 30056668Sshin child_lastactive = parent_lastactive = time(NULL); 30156668Sshin 30256668Sshin cpid = fork(); 30356668Sshin switch (cpid) { 30456668Sshin case -1: 30595023Ssuz exit_failure("tcp_relay: can't fork grand child: %s", 30695023Ssuz strerror(errno)); 30756668Sshin /* NOTREACHED */ 30856668Sshin case 0: 30956668Sshin /* child process: relay going traffic */ 31056668Sshin ppid = getppid(); 31156668Sshin /* this is child so reopen log */ 31256668Sshin closelog(); 31356668Sshin openlog(logname, LOG_PID | LOG_NOWAIT, LOG_DAEMON); 31456668Sshin relay(s_src, s_dst, service, 1); 31556668Sshin /* NOTREACHED */ 31656668Sshin default: 31756668Sshin /* parent process: relay coming traffic */ 31856668Sshin ppid = (pid_t)0; 31956668Sshin signal(SIGUSR1, sig_ctimeout); 32056668Sshin signal(SIGCHLD, sig_child); 32156668Sshin relay(s_dst, s_src, service, 0); 32256668Sshin /* NOTREACHED */ 32356668Sshin } 32456668Sshin} 325