Merge branch 'tor-fw-squashed2'

Conflicts:
	src/common/util.c
This commit is contained in:
Nick Mathewson 2010-09-30 16:22:39 -04:00
commit 495e630a49
21 changed files with 1670 additions and 2 deletions

6
.gitignore vendored
View File

@ -161,6 +161,12 @@
/src/tools/Makefile
/src/tools/Makefile.in
# /src/tools/tor-fw-helper/
/src/tools/tor-fw-helper/tor-fw-helper
/src/tools/tor-fw-helper/tor-fw-helper.exe
/src/tools/tor-fw-helper/Makefile
/src/tools/tor-fw-helper/Makefile.in
# /src/win32/
/src/win32/Makefile
/src/win32/Makefile.in

View File

@ -68,7 +68,8 @@ check-spaces:
src/common/*.h \
src/common/[^asO]*.c src/common/address.c \
src/or/[^e]*.[ch] src/or/eventdns_tor.h \
src/test/test*.[ch] src/tools/*.[ch]
src/test/test*.[ch] src/tools/*.[ch] \
src/tools/tor-fw-helper/*.[ch]
check-docs:
./contrib/checkOptionDocs.pl

14
changes/tor-fw-helper Normal file
View File

@ -0,0 +1,14 @@
o Major features:
- Tor now has the ability to wrangle NAT devices like a good network cowbot
with the tor-fw-helper tool. The tor-fw-helper tool supports Apple's
NAT-PMP protocol and the UPnP standard for TCP port mapping. This
optional tool may be enabled at compile time by configuring with
'--enable-upnp' or '--enable-natpmp' or with both. This tool may be
called by hand or by Tor. By configuring the PortForwarding option, Tor
will launch the helper on a regular basis to ensure that the NAT mapping
is regularly updated. Additionally, a user may also specify an
alternative helper by using the PortForwardingHelper option. The helper
may be specified by name or with the full path to the helper. The default
helper is named 'tor-fw-helper' and any alternative helper must take the
tor-fw-helper-spec.txt into account.

View File

@ -59,6 +59,24 @@ AC_ARG_ENABLE(asciidoc,
*) AC_MSG_ERROR(bad value for --disable-asciidoc) ;;
esac], [asciidoc=true])
# By default, we're not ready to ship a NAT-PMP aware Tor
AC_ARG_ENABLE(nat-pmp,
AS_HELP_STRING(--enable-nat-pmp, enable NAT-PMP support),
[case "${enableval}" in
yes) natpmp=true ;;
no) natpmp=false ;;
* ) AC_MSG_ERROR(bad value for --enable-nat-pmp) ;;
esac], [natpmp=false])
# By default, we're not ready to ship a UPnP aware Tor
AC_ARG_ENABLE(upnp,
AS_HELP_STRING(--enable-upnp, enable UPnP support),
[case "${enableval}" in
yes) upnp=true ;;
no) upnp=false ;;
* ) AC_MSG_ERROR(bad value for --enable-upnp) ;;
esac], [upnp=false])
AC_ARG_ENABLE(threads,
AS_HELP_STRING(--disable-threads, disable multi-threading support))
@ -135,6 +153,11 @@ AC_PATH_PROG([A2X], [a2x], none)
AM_CONDITIONAL(USE_ASCIIDOC, test x$asciidoc = xtrue)
AM_CONDITIONAL(USE_FW_HELPER, test x$natpmp = xtrue || test x$upnp = xtrue)
AM_CONDITIONAL(NAT_PMP, test x$natpmp = xtrue)
AM_CONDITIONAL(MINIUPNPC, test x$upnp = xtrue)
AM_PROG_CC_C_O
AC_PATH_PROG([SHA1SUM], [sha1sum], none)
AC_PATH_PROG([OPENSSL], [openssl], none)
@ -440,6 +463,44 @@ AC_SUBST(TOR_ZLIB_LIBS)
dnl Make sure to enable support for large off_t if available.
dnl ------------------------------------------------------
dnl Where do you live, libnatpmp? And how do we call you?
dnl There are no packages for Debian or Redhat as of this patch
if test "$natpmp" = "true"; then
AC_DEFINE(NAT_PMP, 1, [Define to 1 if we are building with nat-pmp.])
TOR_SEARCH_LIBRARY(libnatpmp, $trylibnatpmpdir, [-lnatpmp],
[#include <natpmp.h>],
[#include <natpmp.h>],
[ int r;
natpmp_t natpmp;
natpmpresp_t response;
r = initnatpmp(&natpmp);],
[printf("initnatpmp() returned %d (%s)\n", r, r?"FAILED":"SUCCESS");
exit(0);],
[--with-libnatpmp-dir],
[/usr/lib/])
fi
dnl ------------------------------------------------------
dnl Where do you live, libminiupnpc? And how do we call you?
dnl There are no packages for Debian or Redhat as of this patch
if test "$upnp" = "true"; then
AC_DEFINE(MINIUPNPC, 1, [Define to 1 if we are building with UPnP.])
TOR_SEARCH_LIBRARY(libminiupnpc, $trylibminiupnpcdir, [-lminiupnpc],
[#include <miniupnpc/miniwget.h>
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h>],
[void upnpDiscover(int delay, const char * multicastif,
const char * minissdpdsock, int sameport);],
[upnpDiscover(1, 0, 0, 0); exit(0);],
[--with-libminiupnpc-dir],
[/usr/lib/])
fi
AC_SYS_LARGEFILE
AC_CHECK_HEADERS(unistd.h string.h signal.h sys/stat.h sys/types.h fcntl.h sys/fcntl.h sys/time.h errno.h assert.h time.h, , AC_MSG_WARN(Some headers were not found, compilation may fail. If compilation succeeds, please send your orconfig.h to the developers so we can fix this warning.))
@ -967,7 +1028,7 @@ fi
CPPFLAGS="$CPPFLAGS $TOR_CPPFLAGS_libevent $TOR_CPPFLAGS_openssl $TOR_CPPFLAGS_zlib"
AC_CONFIG_FILES([Makefile tor.spec Doxyfile contrib/tor.sh contrib/torctl contrib/torify contrib/tor.logrotate contrib/Makefile contrib/osx/Makefile contrib/osx/TorBundleDesc.plist contrib/osx/TorBundleInfo.plist contrib/osx/TorDesc.plist contrib/osx/TorInfo.plist contrib/osx/TorStartupDesc.plist src/config/torrc.sample src/Makefile doc/Makefile doc/spec/Makefile src/config/Makefile src/common/Makefile src/or/Makefile src/test/Makefile src/win32/Makefile src/tools/Makefile contrib/suse/Makefile contrib/suse/tor.sh])
AC_CONFIG_FILES([Makefile tor.spec Doxyfile contrib/tor.sh contrib/torctl contrib/torify contrib/tor.logrotate contrib/Makefile contrib/osx/Makefile contrib/osx/TorBundleDesc.plist contrib/osx/TorBundleInfo.plist contrib/osx/TorDesc.plist contrib/osx/TorInfo.plist contrib/osx/TorStartupDesc.plist src/config/torrc.sample src/Makefile doc/Makefile doc/spec/Makefile src/config/Makefile src/common/Makefile src/or/Makefile src/test/Makefile src/win32/Makefile src/tools/Makefile src/tools/tor-fw-helper/Makefile contrib/suse/Makefile contrib/suse/tor.sh])
AC_OUTPUT
if test -x /usr/bin/perl && test -x ./contrib/updateVersions.pl ; then

View File

@ -0,0 +1,57 @@
Tor's (little) Firewall Helper specification
Jacob Appelbaum
0. Preface
This document describes issues faced by Tor users who are behind NAT devices
and wish to share their resources with the rest of the Tor network. It also
explains a possible solution for some NAT devices.
1. Overview
Tor users often wish to relay traffic for the Tor network and their upstream
firewall thwarts their attempted generosity. Automatic port forwarding
configuration for many consumer NAT devices is often available with two common
protocols NAT-PMP[0] and UPnP[1].
2. Implementation
tor-fw-helper is a program that implements basic port forwarding requests; it
may be used alone or called from Tor itself.
2.1 Output format
When tor-fw-helper has completed the requested action successfully, it will
report the following message to standard output:
tor-fw-helper: SUCCESS
If tor-fw-helper was unable to complete the requested action successfully, it
will report the following message to standard error:
tor-fw-helper: FAILURE
All informational messages are printed to standard output; all error messages
are printed to standard error. Messages other than SUCCESS and FAILURE
may be printed by any compliant tor-fw-helper.
2.2 Output format stability
The above SUCCESS and FAILURE messages are the only stable output formats
provided by this specification. tor-fw-helper-spec compliant implementations
must return SUCCESS or FAILURE as defined above.
3. Security Concerns
It is probably best to hand configure port forwarding and in the process, we
suggest disabling NAT-PMP and/or UPnP. This is of course absolutely confusing
to users and so we support automatic, non-authenticated NAT port mapping
protocols with compliant tor-fw-helper applications.
NAT should not be considered a security boundary. NAT-PMP and UPnP are hacks
to deal with the shortcomings of user education about TCP/IP, IPv4 shortages,
and of course, NAT devices that suffer from horrible user interface design.
[0] http://en.wikipedia.org/wiki/NAT_Port_Mapping_Protocol
[1] http://en.wikipedia.org/wiki/Universal_Plug_and_Play

68
doc/tor-fw-helper.1.txt Normal file
View File

@ -0,0 +1,68 @@
// Copyright (c) The Tor Project, Inc.
// See LICENSE for licensing information
// This is an asciidoc file used to generate the manpage/html reference.
// Learn asciidoc on http://www.methods.co.nz/asciidoc/userguide.html
tor-fw-helper(1)
==============
Jacob Appelbaum
NAME
----
tor-fw-helper - Manage upstream firewall/NAT devices
SYNOPSIS
--------
**tor-fw-helper** [-h|--help] [-T|--test] [-v|--verbose] [-g|--fetch-public-ip]
-i|--internal-or-port __TCP port__ [-e|--external-or-port _TCP port_]
[-d|--internal-dir-port _TCP port_] [-p|--external-dir-port _TCP port_]
DESCRIPTION
-----------
**tor-fw-helper** currently supports Apple's NAT-PMP protocol and the UPnP
standard for TCP port mapping. It is written as the reference implementation of
tor-fw-helper-spec.txt and conforms to that loose plugin API. If your network
supports either NAT-PMP or UPnP, tor-fw-helper will attempt to automatically
map the required TCP ports for Tor's Or and Dir ports. +
OPTIONS
-------
**-h** or **--help**::
Display help text and exit.
**-v**::
Display verbose output.
**-T** or **--test**::
Display test information and print the test information in
tor-fw-helper.log
**-g** or **--fetch-public-ip**::
Fetch the the public ip address for each supported NAT helper method.
**-i** or **--internal-or-port** __port__::
Inform **tor-fw-helper** of your internal OR port. This is the only
required argument.
**-e** or **--external-or-port** __port__::
Inform **tor-fw-helper** of your external OR port.
**-d** or **--internal-dir-port** __port__::
Inform **tor-fw-helper** of your internal Dir port.
**-p** or **--external-dir-port** __port__::
Inform **tor-fw-helper** of your external Dir port.
BUGS
----
This probably doesn't run on Windows. That's not a big issue, since we don't
really want to deal with Windows before October 2010 anyway.
SEE ALSO
--------
**tor**(1) +
See also the "tor-fw-helper-spec.txt" file, distributed with Tor.
AUTHORS
-------
Jacob Appelbaum <jacob@torproject.org>, Steven J. Murdoch <Steven.Murdoch@cl.cam.ac.uk>

View File

@ -873,6 +873,18 @@ is non-zero):
specified in ORPort. (Default: 0.0.0.0) This directive can be specified
multiple times to bind to multiple addresses/ports.
**PortForwarding** **0**|**1**::
Attempt to automatically forward the DirPort and ORPort on a NAT router
connecting this Tor server to the Internet. If set, Tor will try both
NAT-PMP (common on Apple routers) and UPnP (common on routers from other
manufacturers). (Default: 0)
**PortForwardingHelper** __filename__|__pathname__::
If PortForwarding is set, use this executable to configure the forwarding.
If set to a filename, the system path will be searched for the executable.
If set to a path, only the specified path will be executed.
(Default: tor-fw-helper)
**PublishServerDescriptor** **0**|**1**|**v1**|**v2**|**v3**|**bridge**|**hidserv**,**...**::
This option is only considered if you have an ORPort defined. You can
choose multiple arguments, separated by commas.

View File

@ -14,6 +14,7 @@
#define _GNU_SOURCE
#include "orconfig.h"
#define UTIL_PRIVATE
#include "util.h"
#include "torlog.h"
#undef log
@ -2878,3 +2879,411 @@ load_windows_system_library(const TCHAR *library_name)
}
#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)
{
#ifdef MS_WINDOWS
(void) filename; (void) stdout_read; (void) stderr_read; (void) argv;
log_warn(LD_BUG, "not yet implemented on Windows.");
return -1;
#else
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;
#endif
}
/** 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)
{
#ifdef MS_WINDOWS
(void) filename; (void) dir_port; (void) or_port; (void) now;
(void) tor_spawn_background;
(void) log_from_pipe;
log_warn(LD_GENERAL, "Sorry, port forwarding is not yet supported "
"on windows.");
#else
/* 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;
}
}
#endif
}

View File

@ -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

View File

@ -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"),

View File

@ -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. */

View File

@ -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. */

View File

@ -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
};

View File

@ -16,3 +16,7 @@ tor_checkkey_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \
@TOR_LDFLAGS_libevent@
tor_checkkey_LDADD = ../common/libor.a ../common/libor-crypto.a \
-lm @TOR_ZLIB_LIBS@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@
SUBDIRS = tor-fw-helper
DIST_SUBDIRS = tor-fw-helper

View File

@ -0,0 +1,33 @@
if USE_FW_HELPER
bin_PROGRAMS = tor-fw-helper
else
bin_PROGRAMS =
endif
tor_fw_helper_SOURCES = tor-fw-helper.c \
tor-fw-helper-natpmp.c tor-fw-helper-upnp.c
tor_fw_helper_INCLUDES = tor-fw-helper.h tor-fw-helper-natpmp.h tor-fw-helper-upnp.h
if NAT_PMP
nat_pmp_ldflags = @TOR_LDFLAGS_libnatpmp@
nat_pmp_ldadd = -lnatpmp
nat_pmp_cppflags = @TOR_CPPFLAGS_libnatpmp@
else
nat_pmp_ldflags =
nat_pmp_ldadd =
nat_pmp_cppflags =
endif
if MINIUPNPC
miniupnpc_ldflags = @TOR_LDFLAGS_libminiupnpc@
miniupnpc_ldadd = -lminiupnpc -lm
miniupnpc_cppflags = @TOR_CPPFLAGS_libminiupnpc@
else
miniupnpc_ldflags =
miniupnpc_ldadd =
miniupnpc_cppflags =
endif
tor_fw_helper_LDFLAGS = $(nat_pmp_ldflags) $(miniupnpc_ldflags)
tor_fw_helper_LDADD = $(nat_pmp_ldadd) $(miniupnpc_ldadd) ../../common/libor.a @TOR_LIB_WS32@
tor_fw_helper_CPPFLAGS = $(nat_pmp_cppflags) $(miniupnpc_cppflags)

View File

@ -0,0 +1,233 @@
/* Copyright (c) 2010, Jacob Appelbaum, Steven J. Murdoch.
* Copyright (c) 2010, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file tor-fw-helper-natpmp.c
* \brief The implementation of our NAT-PMP firewall helper.
**/
#include "orconfig.h"
#ifdef NAT_PMP
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
// debugging stuff
#include <assert.h>
#include "tor-fw-helper.h"
#include "tor-fw-helper-natpmp.h"
/** This hooks NAT-PMP into our multi-backend API. */
static tor_fw_backend_t tor_natpmp_backend = {
"natpmp",
sizeof(struct natpmp_state_t),
tor_natpmp_init,
tor_natpmp_cleanup,
tor_natpmp_fetch_public_ip,
tor_natpmp_add_tcp_mapping
};
/** Return the backend for NAT-PMP. */
const tor_fw_backend_t *
tor_fw_get_natpmp_backend(void)
{
return &tor_natpmp_backend;
}
/** Initialize the NAT-PMP backend and store the results in
* <b>backend_state</b>.*/
int
tor_natpmp_init(tor_fw_options_t *tor_fw_options, void *backend_state)
{
natpmp_state_t *state = (natpmp_state_t *) backend_state;
int r = 0;
memset(&(state->natpmp), 0, sizeof(natpmp_t));
memset(&(state->response), 0, sizeof(natpmpresp_t));
state->init = 0;
state->protocol = NATPMP_PROTOCOL_TCP;
state->lease = NATPMP_DEFAULT_LEASE;
if (tor_fw_options->verbose)
fprintf(stdout, "V: natpmp init...\n");
r = initnatpmp(&(state->natpmp));
if (r == 0) {
state->init = 1;
fprintf(stdout, "tor-fw-helper: natpmp initialized...\n");
return r;
} else {
fprintf(stderr, "tor-fw-helper: natpmp failed to initialize...\n");
return r;
}
}
/** Tear down the NAT-PMP connection stored in <b>backend_state</b>.*/
int
tor_natpmp_cleanup(tor_fw_options_t *tor_fw_options, void *backend_state)
{
natpmp_state_t *state = (natpmp_state_t *) backend_state;
int r = 0;
if (tor_fw_options->verbose)
fprintf(stdout, "V: natpmp cleanup...\n");
r = closenatpmp(&(state->natpmp));
if (tor_fw_options->verbose)
fprintf(stdout, "V: closing natpmp socket: %d\n", r);
return r;
}
/** Use select() to wait until we can read on fd. */
static int
wait_until_fd_readable(int fd, struct timeval *timeout)
{
int r;
fd_set fds;
if (fd >= FD_SETSIZE) {
fprintf(stderr, "E: NAT-PMP FD_SETSIZE error %d\n", fd);
return -1;
}
FD_ZERO(&fds);
FD_SET(fd, &fds);
r = select(fd+1, &fds, NULL, NULL, timeout);
if (r == -1) {
fprintf(stdout, "V: select failed in wait_until_fd_readable: %s\n",
strerror(errno));
return -1;
}
/* XXXX we should really check to see whether fd was readable, or we timed
out. */
return 0;
}
/** Add a TCP port mapping for a single port stored in <b>tor_fw_options</b>
* using the <b>natpmp_t</b> stored in <b>backend_state</b>. */
int
tor_natpmp_add_tcp_mapping(tor_fw_options_t *tor_fw_options,
void *backend_state)
{
natpmp_state_t *state = (natpmp_state_t *) backend_state;
int r = 0;
int x = 0;
int sav_errno;
struct timeval timeout;
if (tor_fw_options->verbose)
fprintf(stdout, "V: sending natpmp portmapping request...\n");
r = sendnewportmappingrequest(&(state->natpmp), state->protocol,
tor_fw_options->internal_port,
tor_fw_options->external_port,
state->lease);
if (tor_fw_options->verbose)
fprintf(stdout, "tor-fw-helper: NAT-PMP sendnewportmappingrequest "
"returned %d (%s)\n", r, r==12?"SUCCESS":"FAILED");
do {
getnatpmprequesttimeout(&(state->natpmp), &timeout);
x = wait_until_fd_readable(state->natpmp.s, &timeout);
if (x == -1)
return -1;
if (tor_fw_options->verbose)
fprintf(stdout, "V: attempting to readnatpmpreponseorretry...\n");
r = readnatpmpresponseorretry(&(state->natpmp), &(state->response));
sav_errno = errno;
if (r<0 && r!=NATPMP_TRYAGAIN) {
fprintf(stderr, "E: readnatpmpresponseorretry failed %d\n", r);
fprintf(stderr, "E: errno=%d '%s'\n", sav_errno,
strerror(sav_errno));
}
} while (r == NATPMP_TRYAGAIN);
if (r != 0) {
/* XXX TODO: NATPMP_* should be formatted into useful error strings */
fprintf(stderr, "E: NAT-PMP It appears that something went wrong:"
" %d\n", r);
if (r == -51)
fprintf(stderr, "E: NAT-PMP It appears that the request was "
"unauthorized\n");
return r;
}
if (r == NATPMP_SUCCESS) {
fprintf(stdout, "tor-fw-helper: NAT-PMP mapped public port %hu to"
" localport %hu liftime %u\n",
(state->response).pnu.newportmapping.mappedpublicport,
(state->response).pnu.newportmapping.privateport,
(state->response).pnu.newportmapping.lifetime);
}
tor_fw_options->nat_pmp_status = 1;
return r;
}
/** Fetch our likely public IP from our upstream NAT-PMP enabled NAT device.
* Use the connection context stored in <b>backend_state</b>. */
int
tor_natpmp_fetch_public_ip(tor_fw_options_t *tor_fw_options,
void *backend_state)
{
int r = 0;
int x = 0;
int sav_errno;
natpmp_state_t *state = (natpmp_state_t *) backend_state;
struct timeval timeout;
r = sendpublicaddressrequest(&(state->natpmp));
fprintf(stdout, "tor-fw-helper: NAT-PMP sendpublicaddressrequest returned"
" %d (%s)\n", r, r==2?"SUCCESS":"FAILED");
do {
getnatpmprequesttimeout(&(state->natpmp), &timeout);
x = wait_until_fd_readable(state->natpmp.s, &timeout);
if (x == -1)
return -1;
if (tor_fw_options->verbose)
fprintf(stdout, "V: NAT-PMP attempting to read reponse...\n");
r = readnatpmpresponseorretry(&(state->natpmp), &(state->response));
sav_errno = errno;
if (tor_fw_options->verbose)
fprintf(stdout, "V: NAT-PMP readnatpmpresponseorretry returned"
" %d\n", r);
if ( r < 0 && r != NATPMP_TRYAGAIN) {
fprintf(stderr, "E: NAT-PMP readnatpmpresponseorretry failed %d\n",
r);
fprintf(stderr, "E: NAT-PMP errno=%d '%s'\n", sav_errno,
strerror(sav_errno));
}
} while (r == NATPMP_TRYAGAIN );
if (r != 0) {
fprintf(stderr, "E: NAT-PMP It appears that something went wrong:"
" %d\n", r);
return r;
}
fprintf(stdout, "tor-fw-helper: ExternalIPAddress = %s\n",
inet_ntoa((state->response).pnu.publicaddress.addr));
tor_fw_options->public_ip_status = 1;
if (tor_fw_options->verbose) {
fprintf(stdout, "V: result = %u\n", r);
fprintf(stdout, "V: type = %u\n", (state->response).type);
fprintf(stdout, "V: resultcode = %u\n", (state->response).resultcode);
fprintf(stdout, "V: epoch = %u\n", (state->response).epoch);
}
return r;
}
#endif

View File

@ -0,0 +1,47 @@
/* Copyright (c) 2010, Jacob Appelbaum, Steven J. Murdoch.
* Copyright (c) 2010, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file tor-fw-helper-natpmp.h
**/
#ifdef NAT_PMP
#ifndef _TOR_FW_HELPER_NATPMP_H
#define _TOR_FW_HELPER_NATPMP_H
#include <natpmp.h>
/** This is the default NAT-PMP lease time in seconds. */
#define NATPMP_DEFAULT_LEASE 3600
/** NAT-PMP has many codes for success; this is one of them. */
#define NATPMP_SUCCESS 0
/** This is our NAT-PMP meta structure - it holds our request data, responses,
* various NAT-PMP parameters, and of course the status of the motion in the
* NAT-PMP ocean. */
typedef struct natpmp_state_t {
natpmp_t natpmp;
natpmpresp_t response;
int fetch_public_ip;
int status;
int init; /**< Have we been initialized? */
int protocol; /**< This will only be TCP. */
int lease;
} natpmp_state_t;
const tor_fw_backend_t *tor_fw_get_natpmp_backend(void);
int tor_natpmp_init(tor_fw_options_t *tor_fw_options, void *backend_state);
int tor_natpmp_cleanup(tor_fw_options_t *tor_fw_options, void *backend_state);
int tor_natpmp_add_tcp_mapping(tor_fw_options_t *tor_fw_options,
void *backend_state);
int tor_natpmp_fetch_public_ip(tor_fw_options_t *tor_fw_options,
void *backend_state);
#endif
#endif

View File

@ -0,0 +1,186 @@
/* Copyright (c) 2010, Jacob Appelbaum, Steven J. Murdoch.
* Copyright (c) 2010, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file tor-fw-helper-upnp.c
* \brief The implementation of our UPnP firewall helper.
**/
#include "orconfig.h"
#ifdef MINIUPNPC
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "compat.h"
#include "tor-fw-helper.h"
#include "tor-fw-helper-upnp.h"
/** UPnP timeout value. */
#define UPNP_DISCOVER_TIMEOUT 2000
/** Description of the port mapping in the UPnP table. */
#define UPNP_DESC "Tor relay"
/* XXX TODO: We should print these as a useful user string when we return the
* number to a user */
/** Magic numbers as miniupnpc return codes. */
#define UPNP_ERR_SUCCESS 0
#define UPNP_ERR_NODEVICESFOUND 1
#define UPNP_ERR_NOIGDFOUND 2
#define UPNP_ERR_ADDPORTMAPPING 3
#define UPNP_ERR_GETPORTMAPPING 4
#define UPNP_ERR_DELPORTMAPPING 5
#define UPNP_ERR_GETEXTERNALIP 6
#define UPNP_ERR_INVAL 7
#define UPNP_ERR_OTHER 8
#define UPNP_SUCCESS 1
/** This hooks miniupnpc into our multi-backend API. */
static tor_fw_backend_t tor_miniupnp_backend = {
"miniupnp",
sizeof(struct miniupnpc_state_t),
tor_upnp_init,
tor_upnp_cleanup,
tor_upnp_fetch_public_ip,
tor_upnp_add_tcp_mapping
};
/** Return the backend for miniupnp. */
const tor_fw_backend_t *
tor_fw_get_miniupnp_backend(void)
{
return &tor_miniupnp_backend;
}
/** Initialize the UPnP backend and store the results in
* <b>backend_state</b>.*/
int
tor_upnp_init(tor_fw_options_t *options, void *backend_state)
{
/*
This leaks the user agent from the client to the router - perhaps we don't
want to do that? eg:
User-Agent: Ubuntu/10.04, UPnP/1.0, MiniUPnPc/1.4
*/
miniupnpc_state_t *state = (miniupnpc_state_t *) backend_state;
struct UPNPDev *devlist;
int r;
memset(&(state->urls), 0, sizeof(struct UPNPUrls));
memset(&(state->data), 0, sizeof(struct IGDdatas));
state->init = 0;
devlist = upnpDiscover(UPNP_DISCOVER_TIMEOUT, NULL, NULL, 0);
if (NULL == devlist) {
fprintf(stderr, "E: upnpDiscover returned: NULL\n");
return UPNP_ERR_NODEVICESFOUND;
}
assert(options);
r = UPNP_GetValidIGD(devlist, &(state->urls), &(state->data),
state->lanaddr, UPNP_LANADDR_SZ);
fprintf(stdout, "tor-fw-helper: UPnP GetValidIGD returned: %d (%s)\n", r,
r==UPNP_SUCCESS?"SUCCESS":"FAILED");
freeUPNPDevlist(devlist);
if (r != 1 && r != 2)
return UPNP_ERR_NOIGDFOUND;
state->init = 1;
return UPNP_ERR_SUCCESS;
}
/** Tear down the UPnP connection stored in <b>backend_state</b>.*/
int
tor_upnp_cleanup(tor_fw_options_t *options, void *backend_state)
{
miniupnpc_state_t *state = (miniupnpc_state_t *) backend_state;
assert(options);
if (state->init)
FreeUPNPUrls(&(state->urls));
state->init = 0;
return UPNP_ERR_SUCCESS;
}
/** Fetch our likely public IP from our upstream UPnP IGD enabled NAT device.
* Use the connection context stored in <b>backend_state</b>. */
int
tor_upnp_fetch_public_ip(tor_fw_options_t *options, void *backend_state)
{
miniupnpc_state_t *state = (miniupnpc_state_t *) backend_state;
int r;
char externalIPAddress[16];
if (!state->init) {
r = tor_upnp_init(options, state);
if (r != UPNP_ERR_SUCCESS)
return r;
}
r = UPNP_GetExternalIPAddress(state->urls.controlURL,
state->data.first.servicetype,
externalIPAddress);
if (r != UPNPCOMMAND_SUCCESS)
goto err;
if (externalIPAddress[0]) {
fprintf(stdout, "tor-fw-helper: ExternalIPAddress = %s\n",
externalIPAddress); tor_upnp_cleanup(options, state);
options->public_ip_status = 1;
return UPNP_ERR_SUCCESS;
} else {
goto err;
}
err:
tor_upnp_cleanup(options, state);
return UPNP_ERR_GETEXTERNALIP;
}
/** Add a TCP port mapping for a single port stored in <b>tor_fw_options</b>
* and store the results in <b>backend_state</b>. */
int
tor_upnp_add_tcp_mapping(tor_fw_options_t *options, void *backend_state)
{
miniupnpc_state_t *state = (miniupnpc_state_t *) backend_state;
int r;
char internal_port_str[6];
char external_port_str[6];
if (!state->init) {
r = tor_upnp_init(options, state);
if (r != UPNP_ERR_SUCCESS)
return r;
}
if (options->verbose)
fprintf(stdout, "V: internal port: %d, external port: %d\n",
(int)options->internal_port, (int)options->external_port);
tor_snprintf(internal_port_str, sizeof(internal_port_str),
"%d", (int)options->internal_port);
tor_snprintf(external_port_str, sizeof(external_port_str),
"%d", (int)options->external_port);
r = UPNP_AddPortMapping(state->urls.controlURL,
state->data.first.servicetype,
external_port_str, internal_port_str,
state->lanaddr, UPNP_DESC, "TCP", 0);
if (r != UPNPCOMMAND_SUCCESS)
return UPNP_ERR_ADDPORTMAPPING;
options->upnp_status = 1;
return UPNP_ERR_SUCCESS;
}
#endif

View File

@ -0,0 +1,43 @@
/* Copyright (c) 2010, Jacob Appelbaum, Steven J. Murdoch.
* Copyright (c) 2010, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file tor-fw-helper-upnp.h
* \brief The main header for our firewall helper.
**/
#ifdef MINIUPNPC
#ifndef _TOR_FW_HELPER_UPNP_H
#define _TOR_FW_HELPER_UPNP_H
#include <miniupnpc/miniwget.h>
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h>
#include <miniupnpc/upnperrors.h>
/** This is a magic number for miniupnpc lan address size. */
#define UPNP_LANADDR_SZ 64
/** This is our miniupnpc meta structure - it holds our request data,
* responses, and various miniupnpc parameters. */
typedef struct miniupnpc_state_t {
struct UPNPUrls urls;
struct IGDdatas data;
char lanaddr[UPNP_LANADDR_SZ];
int init;
} miniupnpc_state_t;
const tor_fw_backend_t *tor_fw_get_miniupnp_backend(void);
int tor_upnp_init(tor_fw_options_t *options, void *backend_state);
int tor_upnp_cleanup(tor_fw_options_t *options, void *backend_state);
int tor_upnp_fetch_public_ip(tor_fw_options_t *options, void *backend_state);
int tor_upnp_add_tcp_mapping(tor_fw_options_t *options, void *backend_state);
#endif
#endif

View File

@ -0,0 +1,363 @@
/* Copyright (c) 2010, Jacob Appelbaum, Steven J. Murdoch.
* Copyright (c) 2010, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file tor-fw-helper.c
* \brief The main wrapper around our firewall helper logic.
**/
/*
* tor-fw-helper is a tool for opening firewalls with NAT-PMP and UPnP; this
* tool is designed to be called by hand or by Tor by way of a exec() at a
* later date.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <getopt.h>
#include <time.h>
#include <string.h>
#include "orconfig.h"
#include "tor-fw-helper.h"
#ifdef NAT_PMP
#include "tor-fw-helper-natpmp.h"
#endif
#ifdef MINIUPNPC
#include "tor-fw-helper-upnp.h"
#endif
/** This is our meta storage type - it holds information about each helper
including the total number of helper backends, function pointers, and helper
state. */
typedef struct backends_t {
/** The total number of backends */
int n_backends;
/** The backend functions as an array */
tor_fw_backend_t backend_ops[MAX_BACKENDS];
/** The internal backend state */
void *backend_state[MAX_BACKENDS];
} backends_t;
/** Initalize each backend helper with the user input stored in <b>options</b>
* and put the results in the <b>backends</b> struct. */
static int
init_backends(tor_fw_options_t *options, backends_t *backends)
{
int n_available = 0;
int i, r, n;
tor_fw_backend_t *backend_ops_list[MAX_BACKENDS];
void *data = NULL;
/* First, build a list of the working backends. */
n = 0;
#ifdef MINIUPNPC
backend_ops_list[n++] = (tor_fw_backend_t *) tor_fw_get_miniupnp_backend();
#endif
#ifdef NAT_PMP
backend_ops_list[n++] = (tor_fw_backend_t *) tor_fw_get_natpmp_backend();
#endif
n_available = n;
/* Now, for each backend that might work, try to initialize it.
* That's how we roll, initialized.
*/
n = 0;
for (i=0; i<n_available; ++i) {
data = calloc(1, backend_ops_list[i]->state_len);
if (!data) {
perror("calloc");
exit(1);
}
r = backend_ops_list[i]->init(options, data);
if (r == 0) {
backends->backend_ops[n] = *backend_ops_list[i];
backends->backend_state[n] = data;
n++;
} else {
free(data);
}
}
backends->n_backends = n;
return n;
}
/** Return the proper commandline switches when the user needs information. */
static void
usage(void)
{
fprintf(stderr, "tor-fw-helper usage:\n"
" [-h|--help]\n"
" [-T|--Test]\n"
" [-v|--verbose]\n"
" [-g|--fetch-public-ip]\n"
" -i|--internal-or-port [TCP port]\n"
" [-e|--external-or-port [TCP port]]\n"
" [-d|--internal-dir-port [TCP port]\n"
" [-p|--external-dir-port [TCP port]]]\n");
}
/** Log commandline options to a hardcoded file <b>tor-fw-helper.log</b> in the
* current working directory. */
static int
log_commandline_options(int argc, char **argv)
{
int i, retval;
FILE *logfile;
time_t now;
/* Open the log file */
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));
for (i = 0; i < argc; i++) {
retval = fprintf(logfile, "ARG: %d: %s\n", i, argv[i]);
if (retval < 0)
goto error;
retval = fprintf(stdout, "ARG: %d: %s\n", i, argv[i]);
if (retval < 0)
goto error;
}
now = time(NULL);
retval = fprintf(logfile, "END: %s\n", ctime(&now));
/* Close and clean up */
retval = fclose(logfile);
return retval;
/* If there was an error during writing */
error:
fclose(logfile);
return -1;
}
/** Iterate over over each of the supported <b>backends</b> and attempt to
* fetch the public ip. */
static void
tor_fw_fetch_public_ip(tor_fw_options_t *tor_fw_options,
backends_t *backends)
{
int i;
int r = 0;
if (tor_fw_options->verbose)
fprintf(stdout, "V: tor_fw_fetch_public_ip\n");
for (i=0; i<backends->n_backends; ++i) {
if (tor_fw_options->verbose) {
fprintf(stdout, "V: running backend_state now: %i\n", i);
fprintf(stdout, "V: size of backend state: %u\n",
(int)(backends->backend_ops)[i].state_len);
fprintf(stdout, "V: backend state name: %s\n",
(char *)(backends->backend_ops)[i].name);
}
r = backends->backend_ops[i].fetch_public_ip(tor_fw_options,
backends->backend_state[i]);
fprintf(stdout, "tor-fw-helper: tor_fw_fetch_public_ip backend %s "
" returned: %i\n", (char *)(backends->backend_ops)[i].name, r);
}
}
/** Iterate over each of the supported <b>backends</b> and attempt to add a
* port forward for the OR port stored in <b>tor_fw_options</b>. */
static void
tor_fw_add_or_port(tor_fw_options_t *tor_fw_options,
backends_t *backends)
{
int i;
int r = 0;
if (tor_fw_options->verbose)
fprintf(stdout, "V: tor_fw_add_or_port\n");
for (i=0; i<backends->n_backends; ++i) {
if (tor_fw_options->verbose) {
fprintf(stdout, "V: running backend_state now: %i\n", i);
fprintf(stdout, "V: size of backend state: %u\n",
(int)(backends->backend_ops)[i].state_len);
fprintf(stdout, "V: backend state name: %s\n",
(const char *) backends->backend_ops[i].name);
}
r = backends->backend_ops[i].add_tcp_mapping(tor_fw_options,
backends->backend_state[i]);
fprintf(stdout, "tor-fw-helper: tor_fw_add_or_port backend %s "
"returned: %i\n", (const char *) backends->backend_ops[i].name, r);
}
}
/** Iterate over each of the supported <b>backends</b> and attempt to add a
* port forward for the Dir port stored in <b>tor_fw_options</b>. */
static void
tor_fw_add_dir_port(tor_fw_options_t *tor_fw_options,
backends_t *backends)
{
int i;
int r = 0;
if (tor_fw_options->verbose)
fprintf(stdout, "V: tor_fw_add_dir_port\n");
for (i=0; i<backends->n_backends; ++i) {
if (tor_fw_options->verbose) {
fprintf(stdout, "V: running backend_state now: %i\n", i);
fprintf(stdout, "V: size of backend state: %u\n",
(int)(backends->backend_ops)[i].state_len);
fprintf(stdout, "V: backend state name: %s\n",
(char *)(backends->backend_ops)[i].name);
}
r = backends->backend_ops[i].add_tcp_mapping(tor_fw_options,
backends->backend_state[i]);
fprintf(stdout, "tor-fw-helper: tor_fw_add_dir_port backend %s "
"returned: %i\n", (const char *)backends->backend_ops[i].name, r);
}
}
int
main(int argc, char **argv)
{
int r = 0;
int c = 0;
tor_fw_options_t tor_fw_options;
backends_t backend_state;
memset(&tor_fw_options, 0, sizeof(tor_fw_options));
while (1) {
int option_index = 0;
static struct option long_options[] =
{
{"verbose", 0, 0, 'v'},
{"help", 0, 0, 'h'},
{"internal-or-port", 1, 0, 'i'},
{"external-or-port", 1, 0, 'e'},
{"internal-dir-port", 1, 0, 'd'},
{"external-dir-port", 1, 0, 'p'},
{"fetch-public-ip", 0, 0, 'g'},
{"test-commandline", 0, 0, 'T'},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, "vhi:e:d:p:gT",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'v': tor_fw_options.verbose = 1; break;
case 'h': tor_fw_options.help = 1; usage(); exit(1); break;
case 'i': sscanf(optarg, "%hu", &tor_fw_options.private_or_port);
break;
case 'e': sscanf(optarg, "%hu", &tor_fw_options.public_or_port);
break;
case 'd': sscanf(optarg, "%hu", &tor_fw_options.private_dir_port);
break;
case 'p': sscanf(optarg, "%hu", &tor_fw_options.public_dir_port);
break;
case 'g': tor_fw_options.fetch_public_ip = 1; break;
case 'T': tor_fw_options.test_commandline = 1; break;
case '?': break;
default : fprintf(stderr, "Unknown option!\n"); usage(); exit(1);
}
}
if (tor_fw_options.verbose) {
fprintf(stderr, "V: tor-fw-helper version %s\n"
"V: We were called with the following arguments:\n"
"V: verbose = %d, help = %d, pub or port = %u, "
"priv or port = %u\n"
"V: pub dir port = %u, priv dir port = %u\n"
"V: fetch_public_ip = %u\n",
tor_fw_version, tor_fw_options.verbose, tor_fw_options.help,
tor_fw_options.private_or_port, tor_fw_options.public_or_port,
tor_fw_options.private_dir_port, tor_fw_options.public_dir_port,
tor_fw_options.fetch_public_ip);
}
if (tor_fw_options.test_commandline) {
return log_commandline_options(argc, argv);
}
/* At the very least, we require an ORPort;
Given a private ORPort, we can ask for a mapping that matches the port
externally.
*/
if (!tor_fw_options.private_or_port && !tor_fw_options.fetch_public_ip) {
fprintf(stderr, "E: We require an ORPort or fetch_public_ip"
" request!\n");
usage();
exit(1);
} else {
/* When we only have one ORPort, internal/external are
set to be the same.*/
if (!tor_fw_options.public_or_port && tor_fw_options.private_or_port) {
if (tor_fw_options.verbose)
fprintf(stdout, "V: We're setting public_or_port = "
"private_or_port.\n");
tor_fw_options.public_or_port = tor_fw_options.private_or_port;
}
}
if (!tor_fw_options.private_dir_port) {
if (tor_fw_options.verbose)
fprintf(stdout, "V: We have no DirPort; no hole punching for "
"DirPorts\n");
} else {
/* When we only have one DirPort, internal/external are
set to be the same.*/
if (!tor_fw_options.public_dir_port && tor_fw_options.private_dir_port) {
if (tor_fw_options.verbose)
fprintf(stdout, "V: We're setting public_or_port = "
"private_or_port.\n");
tor_fw_options.public_dir_port = tor_fw_options.private_dir_port;
}
}
if (tor_fw_options.verbose) {
fprintf(stdout, "V: pub or port = %u, priv or port = %u\n"
"V: pub dir port = %u, priv dir port = %u\n",
tor_fw_options.private_or_port, tor_fw_options.public_or_port,
tor_fw_options.private_dir_port,
tor_fw_options.public_dir_port);
}
// Initalize the various fw-helper backend helpers
r = init_backends(&tor_fw_options, &backend_state);
if (r)
printf("tor-fw-helper: %i NAT traversal helper(s) loaded\n", r);
if (tor_fw_options.fetch_public_ip) {
tor_fw_fetch_public_ip(&tor_fw_options, &backend_state);
}
if (tor_fw_options.private_or_port) {
tor_fw_options.internal_port = tor_fw_options.private_or_port;
tor_fw_options.external_port = tor_fw_options.private_or_port;
tor_fw_add_or_port(&tor_fw_options, &backend_state);
}
if (tor_fw_options.private_dir_port) {
tor_fw_options.internal_port = tor_fw_options.private_dir_port;
tor_fw_options.external_port = tor_fw_options.private_dir_port;
tor_fw_add_dir_port(&tor_fw_options, &backend_state);
}
r = (((tor_fw_options.nat_pmp_status | tor_fw_options.upnp_status)
|tor_fw_options.public_ip_status));
if (r > 0) {
fprintf(stdout, "tor-fw-helper: SUCCESS\n");
} else {
fprintf(stderr, "tor-fw-helper: FAILURE\n");
}
exit(r);
}

View File

@ -0,0 +1,57 @@
/* Copyright (c) 2010, Jacob Appelbaum, Steven J. Murdoch.
* Copyright (c) 2010, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file tor-fw-helper.h
* \brief The main header for our firewall helper.
**/
#ifndef _TOR_FW_HELPER_H
#define _TOR_FW_HELPER_H
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <time.h>
/** The current version of tor-fw-helper. */
#define tor_fw_version "0.1"
/** This is an arbitrary hard limit - We currently have two (NAT-PMP and UPnP).
We're likely going to add the Intel UPnP library but nothing else comes to
mind at the moment. */
#define MAX_BACKENDS 23
/** This is where we store parsed commandline options. */
typedef struct {
int verbose;
int help;
int test_commandline;
uint16_t private_dir_port;
uint16_t private_or_port;
uint16_t public_dir_port;
uint16_t public_or_port;
uint16_t internal_port;
uint16_t external_port;
int fetch_public_ip;
int nat_pmp_status;
int upnp_status;
int public_ip_status;
} tor_fw_options_t;
/** This is our main structure that defines our backend helper API; each helper
* must conform to these public methods if it expects to be handled in a
* non-special way. */
typedef struct tor_fw_backend_t {
const char *name;
size_t state_len;
int (*init)(tor_fw_options_t *options, void *backend_state);
int (*cleanup)(tor_fw_options_t *options, void *backend_state);
int (*fetch_public_ip)(tor_fw_options_t *options, void *backend_state);
int (*add_tcp_mapping)(tor_fw_options_t *options, void *backend_state);
} tor_fw_backend_t;
#endif