Просмотр исходного кода

Merge branch 'tor-fw-squashed2'

Conflicts:
	src/common/util.c
Nick Mathewson 13 лет назад
Родитель
Сommit
495e630a49

+ 6 - 0
.gitignore

@@ -161,6 +161,12 @@
 /src/tools/Makefile
 /src/tools/Makefile
 /src/tools/Makefile.in
 /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/
 /src/win32/Makefile
 /src/win32/Makefile
 /src/win32/Makefile.in
 /src/win32/Makefile.in

+ 2 - 1
Makefile.am

@@ -68,7 +68,8 @@ check-spaces:
 	        src/common/*.h                        \
 	        src/common/*.h                        \
 		src/common/[^asO]*.c src/common/address.c \
 		src/common/[^asO]*.c src/common/address.c \
 		src/or/[^e]*.[ch] src/or/eventdns_tor.h \
 		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:
 check-docs:
 	./contrib/checkOptionDocs.pl
 	./contrib/checkOptionDocs.pl

+ 14 - 0
changes/tor-fw-helper

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

+ 62 - 1
configure.in

@@ -59,6 +59,24 @@ AC_ARG_ENABLE(asciidoc,
         *) AC_MSG_ERROR(bad value for --disable-asciidoc) ;;
         *) AC_MSG_ERROR(bad value for --disable-asciidoc) ;;
       esac], [asciidoc=true])
       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,
 AC_ARG_ENABLE(threads,
      AS_HELP_STRING(--disable-threads, disable multi-threading support))
      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_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([SHA1SUM], [sha1sum], none)
 AC_PATH_PROG([OPENSSL], [openssl], 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 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_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.))
 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"
 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
 AC_OUTPUT
 
 
 if test -x /usr/bin/perl && test -x ./contrib/updateVersions.pl ; then
 if test -x /usr/bin/perl && test -x ./contrib/updateVersions.pl ; then

+ 57 - 0
doc/spec/tor-fw-helper-spec.txt

@@ -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 - 0
doc/tor-fw-helper.1.txt

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

+ 12 - 0
doc/tor.1.txt

@@ -873,6 +873,18 @@ is non-zero):
     specified in ORPort. (Default: 0.0.0.0) This directive can be specified
     specified in ORPort. (Default: 0.0.0.0) This directive can be specified
     multiple times to bind to multiple addresses/ports.
     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**,**...**::
 **PublishServerDescriptor** **0**|**1**|**v1**|**v2**|**v3**|**bridge**|**hidserv**,**...**::
     This option is only considered if you have an ORPort defined. You can
     This option is only considered if you have an ORPort defined. You can
     choose multiple arguments, separated by commas.
     choose multiple arguments, separated by commas.

+ 409 - 0
src/common/util.c

@@ -14,6 +14,7 @@
 #define _GNU_SOURCE
 #define _GNU_SOURCE
 
 
 #include "orconfig.h"
 #include "orconfig.h"
+#define UTIL_PRIVATE
 #include "util.h"
 #include "util.h"
 #include "torlog.h"
 #include "torlog.h"
 #undef log
 #undef log
@@ -2878,3 +2879,411 @@ load_windows_system_library(const TCHAR *library_name)
 }
 }
 #endif
 #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
+}
+

+ 15 - 0
src/common/util.h

@@ -340,10 +340,25 @@ void start_daemon(void);
 void finish_daemon(const char *desired_cwd);
 void finish_daemon(const char *desired_cwd);
 void write_pidfile(char *filename);
 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
 #ifdef MS_WINDOWS
 HANDLE load_windows_system_library(const TCHAR *library_name);
 HANDLE load_windows_system_library(const TCHAR *library_name);
 #endif
 #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);
 const char *libor_get_digests(void);
 
 
 #endif
 #endif

+ 2 - 0
src/or/config.c

@@ -317,6 +317,8 @@ static config_var_t _option_vars[] = {
   V(PerConnBWRate,               MEMUNIT,  "0"),
   V(PerConnBWRate,               MEMUNIT,  "0"),
   V(PidFile,                     STRING,   NULL),
   V(PidFile,                     STRING,   NULL),
   V(TestingTorNetwork,           BOOL,     "0"),
   V(TestingTorNetwork,           BOOL,     "0"),
+  V(PortForwarding,              BOOL,     "0"),
+  V(PortForwardingHelper,        FILENAME, "tor-fw-helper"),
   V(PreferTunneledDirConns,      BOOL,     "1"),
   V(PreferTunneledDirConns,      BOOL,     "1"),
   V(ProtocolWarnings,            BOOL,     "0"),
   V(ProtocolWarnings,            BOOL,     "0"),
   V(PublishServerDescriptor,     CSV,      "1"),
   V(PublishServerDescriptor,     CSV,      "1"),

+ 12 - 0
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_check_for_expired_networkstatus = 0;
   static time_t time_to_write_stats_files = 0;
   static time_t time_to_write_stats_files = 0;
   static time_t time_to_write_bridge_stats = 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 int should_init_bridge_stats = 1;
   static time_t time_to_retry_dns_init = 0;
   static time_t time_to_retry_dns_init = 0;
   or_options_t *options = get_options();
   or_options_t *options = get_options();
@@ -1385,6 +1386,17 @@ run_scheduled_events(time_t now)
 #define BRIDGE_STATUSFILE_INTERVAL (30*60)
 #define BRIDGE_STATUSFILE_INTERVAL (30*60)
     time_to_write_bridge_status_file = now+BRIDGE_STATUSFILE_INTERVAL;
     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. */
 /** Timer: used to invoke second_elapsed_callback() once per second. */

+ 4 - 0
src/or/or.h

@@ -2772,6 +2772,10 @@ typedef struct {
                        * possible. */
                        * possible. */
   int PreferTunneledDirConns; /**< If true, avoid dirservers that don't
   int PreferTunneledDirConns; /**< If true, avoid dirservers that don't
                                * support BEGIN_DIR, when possible. */
                                * 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
   int AllowNonRFC953Hostnames; /**< If true, we allow connections to hostnames
                                 * with weird characters. */
                                 * with weird characters. */
   /** If true, we try resolving hostnames with weird characters. */
   /** If true, we try resolving hostnames with weird characters. */

+ 41 - 0
src/test/test_util.c

@@ -6,6 +6,7 @@
 #include "orconfig.h"
 #include "orconfig.h"
 #define CONTROL_PRIVATE
 #define CONTROL_PRIVATE
 #define MEMPOOL_PRIVATE
 #define MEMPOOL_PRIVATE
+#define UTIL_PRIVATE
 #include "or.h"
 #include "or.h"
 #include "config.h"
 #include "config.h"
 #include "control.h"
 #include "control.h"
@@ -1208,6 +1209,45 @@ test_util_load_win_lib(void *ptr)
 }
 }
 #endif
 #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)                                               \
 #define UTIL_LEGACY(name)                                               \
   { #name, legacy_test_helper, 0, &legacy_setup, test_util_ ## name }
   { #name, legacy_test_helper, 0, &legacy_setup, test_util_ ## name }
 
 
@@ -1234,6 +1274,7 @@ struct testcase_t util_tests[] = {
 #ifdef MS_WINDOWS
 #ifdef MS_WINDOWS
   UTIL_TEST(load_win_lib, 0),
   UTIL_TEST(load_win_lib, 0),
 #endif
 #endif
+  UTIL_TEST(exit_status, 0),
   END_OF_TESTCASES
   END_OF_TESTCASES
 };
 };
 
 

+ 4 - 0
src/tools/Makefile.am

@@ -16,3 +16,7 @@ tor_checkkey_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \
         @TOR_LDFLAGS_libevent@
         @TOR_LDFLAGS_libevent@
 tor_checkkey_LDADD = ../common/libor.a ../common/libor-crypto.a \
 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@
         -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
+

+ 33 - 0
src/tools/tor-fw-helper/Makefile.am

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

+ 233 - 0
src/tools/tor-fw-helper/tor-fw-helper-natpmp.c

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

+ 47 - 0
src/tools/tor-fw-helper/tor-fw-helper-natpmp.h

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

+ 186 - 0
src/tools/tor-fw-helper/tor-fw-helper-upnp.c

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

+ 43 - 0
src/tools/tor-fw-helper/tor-fw-helper-upnp.h

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

+ 363 - 0
src/tools/tor-fw-helper/tor-fw-helper.c

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

+ 57 - 0
src/tools/tor-fw-helper/tor-fw-helper.h

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