diff options
author | Steven Murdoch <Steven.Murdoch@cl.cam.ac.uk> | 2010-06-16 19:47:06 +0100 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2010-09-30 11:40:37 -0400 |
commit | a6dc00fa75fcb089837c50ba1af7a25e5e6e6b20 (patch) | |
tree | 989f64598746e21913ddab1b3557cb3136a05876 /src | |
parent | 3eaa9a376c756b0b8f836c4a78c3cf4012d6ffd7 (diff) | |
download | tor-a6dc00fa75fcb089837c50ba1af7a25e5e6e6b20.tar.gz tor-a6dc00fa75fcb089837c50ba1af7a25e5e6e6b20.zip |
Start tor-fw-helper in the background, and log whatever it outputs
Diffstat (limited to 'src')
-rw-r--r-- | src/common/util.c | 398 | ||||
-rw-r--r-- | src/common/util.h | 15 | ||||
-rw-r--r-- | src/or/config.c | 2 | ||||
-rw-r--r-- | src/or/main.c | 12 | ||||
-rw-r--r-- | src/or/or.h | 4 | ||||
-rw-r--r-- | src/test/test_util.c | 41 | ||||
-rw-r--r-- | src/tools/tor-fw-helper/tor-fw-helper.c | 2 |
7 files changed, 473 insertions, 1 deletions
diff --git a/src/common/util.c b/src/common/util.c index b4f3052e19..9d47b76246 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -14,6 +14,7 @@ #define _GNU_SOURCE #include "orconfig.h" +#define UTIL_PRIVATE #include "util.h" #include "torlog.h" #undef log @@ -2877,3 +2878,400 @@ load_windows_system_library(const TCHAR *library_name) return LoadLibrary(path); } #endif + +/** Format child_state and saved_errno as a hex string placed in hex_errno. + * Called between fork and _exit, so must be signal-handler safe */ +void +format_helper_exit_status(unsigned char child_state, int saved_errno, + char *hex_errno) +{ + /* Convert errno to be unsigned for hex conversion */ + unsigned int unsigned_errno; + char *cur; + + /* If errno is negative, negate it */ + if (saved_errno < 0) { + unsigned_errno = (unsigned int) -saved_errno; + } else { + unsigned_errno = (unsigned int) saved_errno; + } + + /* Convert errno to hex (start before \n) */ + cur = hex_errno + HEX_ERRNO_SIZE - 2; + + do { + *cur-- = "0123456789ABCDEF"[unsigned_errno % 16]; + unsigned_errno /= 16; + } while (unsigned_errno != 0 && cur >= hex_errno); + + /* Add on the minus side if errno was negative */ + if (saved_errno < 0) + *cur-- = '-'; + + /* Leave a gap */ + *cur-- = '/'; + + /* Convert child_state to hex */ + do { + *cur-- = "0123456789ABCDEF"[child_state % 16]; + child_state /= 16; + } while (child_state != 0 && cur >= hex_errno); +} + +/* Maximum number of file descriptors, if we cannot get it via sysconf() */ +#define DEFAULT_MAX_FD 256 + +#define CHILD_STATE_INIT 0 +#define CHILD_STATE_PIPE 1 +#define CHILD_STATE_MAXFD 2 +#define CHILD_STATE_FORK 3 +#define CHILD_STATE_DUPOUT 4 +#define CHILD_STATE_DUPERR 5 +#define CHILD_STATE_REDIRECT 6 +#define CHILD_STATE_CLOSEFD 7 +#define CHILD_STATE_EXEC 8 +#define CHILD_STATE_FAILEXEC 9 + +#define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code " + +/** Start a program in the background. If <b>filename</b> contains a '/', then + * it will be treated as an absolute or relative path. Otherwise the system + * path will be searched for <b>filename</b>. Returns pid on success, otherwise + * returns -1. + * Some parts of this code are based on the POSIX subprocess module from Python + */ +static int +tor_spawn_background(const char *const filename, int *stdout_read, + int *stderr_read, const char **argv) +{ + pid_t pid; + int stdout_pipe[2]; + int stderr_pipe[2]; + int fd, retval; + ssize_t nbytes; + + const char *error_message = SPAWN_ERROR_MESSAGE; + size_t error_message_length; + + /* Represents where in the process of spawning the program is; + this is used for printing out the error message */ + unsigned char child_state = CHILD_STATE_INIT; + + char hex_errno[HEX_ERRNO_SIZE]; + + static int max_fd = -1; + + /* We do the strlen here because strlen() is not signal handler safe, + and we are not allowed to use unsafe functions between fork and exec */ + error_message_length = strlen(error_message); + + /* Fill hex_errno with spaces, and a trailing newline */ + memset(hex_errno, ' ', sizeof(hex_errno) - 1); + hex_errno[sizeof(hex_errno) - 1] = '\n'; + + child_state = CHILD_STATE_PIPE; + + /* Set up pipe for redirecting stdout and stderr of child */ + retval = pipe(stdout_pipe); + if (-1 == retval) { + log_err(LD_GENERAL, + "Failed to set up pipe for stdout communication with child process: %s", + strerror(errno)); + return -1; + } + + retval = pipe(stderr_pipe); + if (-1 == retval) { + log_err(LD_GENERAL, + "Failed to set up pipe for stderr communication with child process: %s", + strerror(errno)); + return -1; + } + + child_state = CHILD_STATE_MAXFD; + +#ifdef _SC_OPEN_MAX + if (-1 != max_fd) { + max_fd = (int) sysconf(_SC_OPEN_MAX); + if (max_fd == -1) + max_fd = DEFAULT_MAX_FD; + log_warn(LD_GENERAL, + "Cannot find maximum file descriptor, assuming %d", max_fd); + } +#else + max_fd = DEFAULT_MAX_FD; +#endif + + child_state = CHILD_STATE_FORK; + + pid = fork(); + if (0 == pid) { + /* In child */ + + child_state = CHILD_STATE_DUPOUT; + + /* Link child stdout to the write end of the pipe */ + retval = dup2(stdout_pipe[1], STDOUT_FILENO); + if (-1 == retval) + goto error; + + child_state = CHILD_STATE_DUPERR; + + /* Link child stderr to the write end of the pipe */ + retval = dup2(stderr_pipe[1], STDERR_FILENO); + if (-1 == retval) + goto error; + + child_state = CHILD_STATE_REDIRECT; + + /* Link stdin to /dev/null */ + fd = open("/dev/null", O_RDONLY); + if (fd != -1) + dup2(STDIN_FILENO, fd); + else + goto error; + + child_state = CHILD_STATE_CLOSEFD; + + /* Close all other fds, including the read end of the pipe */ + /* TODO: use closefrom if available */ + for (fd = STDERR_FILENO + 1; fd < max_fd; fd++) + close(fd); + + child_state = CHILD_STATE_EXEC; + + /* Call the requested program. We need the cast because + execvp doesn't define argv as const, even though it + does not modify the arguments */ + execvp(filename, (char *const *) argv); + + /* If we got here, the exec or open(/dev/null) failed */ + + child_state = CHILD_STATE_FAILEXEC; + + error: + /* TODO: are we leaking fds from the pipe? */ + + format_helper_exit_status(child_state, errno, hex_errno); + + /* Write the error message. GCC requires that we check the return + value, but there is nothing we can do if it fails */ + nbytes = write(STDOUT_FILENO, error_message, error_message_length); + nbytes = write(STDOUT_FILENO, hex_errno, sizeof(hex_errno)); + + _exit(255); + return -1; /* Never reached, but avoids compiler warning */ + } + + /* In parent */ + + if (-1 == pid) { + log_err(LD_GENERAL, "Failed to fork child process: %s", strerror(errno)); + close(stdout_pipe[0]); + close(stdout_pipe[1]); + close(stderr_pipe[0]); + close(stderr_pipe[1]); + return -1; + } + + /* Return read end of the pipes to caller, and close write end */ + *stdout_read = stdout_pipe[0]; + retval = close(stdout_pipe[1]); + + if (-1 == retval) { + log_err(LD_GENERAL, + "Failed to close write end of stdout pipe in parent process: %s", + strerror(errno)); + /* Do not return -1, because the child is running, so the parent + needs to know about the pid in order to reap it later */ + } + + *stderr_read = stderr_pipe[0]; + retval = close(stderr_pipe[1]); + + if (-1 == retval) { + log_err(LD_GENERAL, + "Failed to close write end of stderr pipe in parent process: %s", + strerror(errno)); + /* Do not return -1, because the child is running, so the parent + needs to know about the pid in order to reap it later */ + } + + return pid; +} + +/** Read from stream, and send lines to log at the specified log level. + * Returns 1 if stream is closed normally, -1 if there is a error reading, and + * 0 otherwise. Handles lines from tor-fw-helper and + * tor_spawn_background() specially. + */ +static int +log_from_pipe(FILE *stream, int severity, const char *executable, + int *child_status) +{ + char buf[256]; + + for (;;) { + char *retval; + retval = fgets(buf, sizeof(buf), stream); + + if (NULL == retval) { + if (feof(stream)) { + /* Program has closed stream (probably it exited) */ + /* TODO: check error */ + fclose(stream); + return 1; + } else { + if (EAGAIN == errno) { + /* Nothing more to read, try again next time */ + return 0; + } else { + /* There was a problem, abandon this child process */ + fclose(stream); + return -1; + } + } + } else { + /* We have some data, log it and keep asking for more */ + size_t len; + + len = strlen(buf); + if (buf[len - 1] == '\n') { + /* Remove the trailing newline */ + buf[len - 1] = '\0'; + } else { + /* No newline; check whether we overflowed the buffer */ + if (!feof(stream)) + log_err(LD_GENERAL, + "Line from port forwarding helper was truncated: %s", buf); + /* TODO: What to do with this error? */ + } + + /* Check if buf starts with SPAWN_ERROR_MESSAGE */ + if (strstr(buf, SPAWN_ERROR_MESSAGE) == buf) { + /* Parse error message */ + int retval, child_state, saved_errno; + retval = sscanf(buf, SPAWN_ERROR_MESSAGE "%d/%d", + &child_state, &saved_errno); + if (retval == 2) { + log_err(LD_GENERAL, + "Failed to start child process \"%s\" in state %d: %s", + executable, child_state, strerror(saved_errno)); + if (child_status) + *child_status = 1; + } else { + /* Failed to parse message from child process, log it as error */ + log_err(LD_GENERAL, + "Unexpected message from port forwarding helper \"%s\": %s", + executable, buf); + } + } else { + log_fn(severity, LD_GENERAL, "Port forwarding helper says: %s", buf); + } + } + } + + /* We should never get here */ + return -1; +} + +void +tor_check_port_forwarding(const char *filename, int dir_port, int or_port, + time_t now) +{ + +/* When fw-helper succeeds, how long do we wait until running it again */ +#define TIME_TO_EXEC_FWHELPER_SUCCESS 300 +/* When fw-helper fails, how long do we wait until running it again */ +#define TIME_TO_EXEC_FWHELPER_FAIL 60 + + static int child_pid = -1; + static FILE *stdout_read = NULL; + static FILE *stderr_read = NULL; + static time_t time_to_run_helper = 0; + int stdout_status, stderr_status, retval; + const char *argv[10]; + char s_dirport[6], s_orport[6]; + + tor_assert(filename); + + /* Set up command line for tor-fw-helper */ + snprintf(s_dirport, sizeof s_dirport, "%d", dir_port); + snprintf(s_orport, sizeof s_orport, "%d", or_port); + + /* TODO: Allow different internal and external ports */ + argv[0] = filename; + argv[1] = "--internal-or-port"; + argv[2] = s_orport; + argv[3] = "--external-or-port"; + argv[4] = s_orport; + argv[5] = "--internal-dir-port"; + argv[6] = s_dirport; + argv[7] = "--external-dir-port"; + argv[8] = s_dirport; + argv[9] = NULL; + + /* Start the child, if it is not already running */ + if (-1 == child_pid && + time_to_run_helper < now) { + int fd_out, fd_err; + + /* Assume tor-fw-helper will succeed, start it later*/ + time_to_run_helper = now + TIME_TO_EXEC_FWHELPER_SUCCESS; + + child_pid = tor_spawn_background(filename, &fd_out, &fd_err, argv); + if (child_pid < 0) { + log_err(LD_GENERAL, "Failed to start port forwarding helper %s", + filename); + child_pid = -1; + return; + } + /* Set stdout/stderr pipes to be non-blocking */ + fcntl(fd_out, F_SETFL, O_NONBLOCK); + fcntl(fd_err, F_SETFL, O_NONBLOCK); + /* Open the buffered IO streams */ + stdout_read = fdopen(fd_out, "r"); + stderr_read = fdopen(fd_err, "r"); + + log_info(LD_GENERAL, + "Started port forwarding helper (%s) with pid %d", filename, child_pid); + } + + /* If child is running, read from its stdout and stderr) */ + if (child_pid > 0) { + /* Read from stdout/stderr and log result */ + retval = 0; + stdout_status = log_from_pipe(stdout_read, LOG_INFO, filename, &retval); + stderr_status = log_from_pipe(stderr_read, LOG_ERR, filename, &retval); + if (retval) { + /* There was a problem in the child process */ + time_to_run_helper = now + TIME_TO_EXEC_FWHELPER_FAIL; + } + + /* Combine the two statuses in order of severity */ + if (-1 == stdout_status || -1 == stderr_status) + /* There was a failure */ + retval = -1; + else if (1 == stdout_status || 1 == stderr_status) + /* stdout or stderr was closed */ + retval = 1; + else + /* Both are fine */ + retval = 0; + + /* If either pipe indicates a failure, act on it */ + if (0 != retval) { + if (1 == retval) { + log_info(LD_GENERAL, "Port forwarding helper terminated"); + } else { + log_err(LD_GENERAL, "Failed to read from port forwarding helper"); + } + + /* TODO: The child might not actually be finished (maybe it failed or + closed stdout/stderr), so maybe we shouldn't start another? */ + child_pid = -1; + } + } +} + + diff --git a/src/common/util.h b/src/common/util.h index 833fd904c7..86555eeb19 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -340,10 +340,25 @@ void start_daemon(void); void finish_daemon(const char *desired_cwd); void write_pidfile(char *filename); +/* Port forwarding */ +void tor_check_port_forwarding(const char *filename, + int dir_port, int or_port, time_t now); + #ifdef MS_WINDOWS HANDLE load_windows_system_library(const TCHAR *library_name); #endif +#ifdef UTIL_PRIVATE +/* Prototypes for private functions only used by util.c (and unit tests) */ +void format_helper_exit_status(unsigned char child_state, + int saved_errno, char *hex_errno); + +/* Space for hex values of child state, a slash, saved_errno (with + leading minus) and newline (no null) */ +#define HEX_ERRNO_SIZE (sizeof(char) * 2 + 1 + \ + 1 + sizeof(int) * 2 + 1) +#endif + const char *libor_get_digests(void); #endif diff --git a/src/or/config.c b/src/or/config.c index 23cad9268b..4f7077153d 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -317,6 +317,8 @@ static config_var_t _option_vars[] = { V(PerConnBWRate, MEMUNIT, "0"), V(PidFile, STRING, NULL), V(TestingTorNetwork, BOOL, "0"), + V(PortForwarding, BOOL, "0"), + V(PortForwardingHelper, FILENAME, "tor-fw-helper"), V(PreferTunneledDirConns, BOOL, "1"), V(ProtocolWarnings, BOOL, "0"), V(PublishServerDescriptor, CSV, "1"), diff --git a/src/or/main.c b/src/or/main.c index 23daf13661..ddd5da3643 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -1026,6 +1026,7 @@ run_scheduled_events(time_t now) static time_t time_to_check_for_expired_networkstatus = 0; static time_t time_to_write_stats_files = 0; static time_t time_to_write_bridge_stats = 0; + static time_t time_to_check_port_forwarding = 0; static int should_init_bridge_stats = 1; static time_t time_to_retry_dns_init = 0; or_options_t *options = get_options(); @@ -1385,6 +1386,17 @@ run_scheduled_events(time_t now) #define BRIDGE_STATUSFILE_INTERVAL (30*60) time_to_write_bridge_status_file = now+BRIDGE_STATUSFILE_INTERVAL; } + + if (time_to_check_port_forwarding < now && + options->PortForwarding && + server_mode(options)) { +#define PORT_FORWARDING_CHECK_INTERVAL 5 + tor_check_port_forwarding(options->PortForwardingHelper, + options->DirPort, + options->ORPort, + now); + time_to_check_port_forwarding = now+PORT_FORWARDING_CHECK_INTERVAL; + } } /** Timer: used to invoke second_elapsed_callback() once per second. */ diff --git a/src/or/or.h b/src/or/or.h index 06845dd60d..673a3920cf 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -2772,6 +2772,10 @@ typedef struct { * possible. */ int PreferTunneledDirConns; /**< If true, avoid dirservers that don't * support BEGIN_DIR, when possible. */ + int PortForwarding; /**< If true, use NAT-PMP or UPnP to automatically + * forward the DirPort and ORPort on the NAT device */ + char *PortForwardingHelper; /** < Filename or full path of the port + forwarding helper executable */ int AllowNonRFC953Hostnames; /**< If true, we allow connections to hostnames * with weird characters. */ /** If true, we try resolving hostnames with weird characters. */ diff --git a/src/test/test_util.c b/src/test/test_util.c index a14d548b8e..68a0ca2984 100644 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@ -6,6 +6,7 @@ #include "orconfig.h" #define CONTROL_PRIVATE #define MEMPOOL_PRIVATE +#define UTIL_PRIVATE #include "or.h" #include "config.h" #include "control.h" @@ -1208,6 +1209,45 @@ test_util_load_win_lib(void *ptr) } #endif +static void +clear_hex_errno(char *hex_errno) +{ + memset(hex_errno, ' ', HEX_ERRNO_SIZE - 2); + hex_errno[HEX_ERRNO_SIZE - 1] = '\n'; + hex_errno[HEX_ERRNO_SIZE] = '\0'; +} + +static void +test_util_exit_status(void *ptr) +{ + char hex_errno[HEX_ERRNO_SIZE + 1]; + + (void)ptr; + + clear_hex_errno(hex_errno); + format_helper_exit_status(0, 0, hex_errno); + tt_str_op(hex_errno, ==, " 0/0\n"); + + clear_hex_errno(hex_errno); + format_helper_exit_status(0, 0x7FFFFFFF, hex_errno); + tt_str_op(hex_errno, ==, " 0/7FFFFFFF\n"); + + clear_hex_errno(hex_errno); + format_helper_exit_status(0xFF, -0x80000000, hex_errno); + tt_str_op(hex_errno, ==, "FF/-80000000\n"); + + clear_hex_errno(hex_errno); + format_helper_exit_status(0x7F, 0, hex_errno); + tt_str_op(hex_errno, ==, " 7F/0\n"); + + clear_hex_errno(hex_errno); + format_helper_exit_status(0x08, -0x242, hex_errno); + tt_str_op(hex_errno, ==, " 8/-242\n"); + + done: + ; +} + #define UTIL_LEGACY(name) \ { #name, legacy_test_helper, 0, &legacy_setup, test_util_ ## name } @@ -1234,6 +1274,7 @@ struct testcase_t util_tests[] = { #ifdef MS_WINDOWS UTIL_TEST(load_win_lib, 0), #endif + UTIL_TEST(exit_status, 0), END_OF_TESTCASES }; diff --git a/src/tools/tor-fw-helper/tor-fw-helper.c b/src/tools/tor-fw-helper/tor-fw-helper.c index 968f19f7f2..4639f3def8 100644 --- a/src/tools/tor-fw-helper/tor-fw-helper.c +++ b/src/tools/tor-fw-helper/tor-fw-helper.c @@ -115,7 +115,7 @@ log_commandline_options(int argc, char **argv) logfile = fopen("tor-fw-helper.log", "a"); if (NULL == logfile) return -1; - + /* Send all commandline arguments to the file */ now = time(NULL); retval = fprintf(logfile, "START: %s\n", ctime(&now)); |