Commit 3eaa9a37 authored by Jacob Appelbaum's avatar Jacob Appelbaum Committed by Nick Mathewson
Browse files

Changes to tor-fw-helper, some based on Nick's review

 * MINIUPNPC rather than the generic UPNP
 * Nick suggested a better abstraction model for tor-fw-helper
 * Fix autoconf to build with either natpmp or miniupnpc
 * Add AM_PROG_CC_C_O to fix automake complaint
 * update spec to address nickm's concern
 * refactor nat-pmp to match upnp state
 * we prefer tor_snprintf to snprintf
 * link properlty for tor_snprintf
 * rename test_commandline_options to log_commandline_options
 * cast this uint as an int
 * detect possible FD_SETSIZE errors
 * make note about future enhancements for natpmp
 * add upnp enhancement note
 * ChangeLog entry
 * doxygen and check-spaces cleanup
 * create tor-fw-helper.1.txt
parent 9cc76cf0
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.
......@@ -153,7 +153,10 @@ AC_PATH_PROG([A2X], [a2x], none)
AM_CONDITIONAL(USE_ASCIIDOC, test x$asciidoc = xtrue)
AM_CONDITIONAL(USE_FW_HELPER, test x$natpmp = xtrue || x$upnp = 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)
......@@ -486,7 +489,7 @@ 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(UPNP, 1, [Define to 1 if we are building with UPnP.])
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>
......
......@@ -33,12 +33,25 @@
tor-fw-helper: FAILURE
All informational messages are printed to standard output; all error messages
are printed to standard error.
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.
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
// 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>
......@@ -7,6 +7,27 @@ 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
tor_fw_helper_LDFLAGS = @TOR_LDFLAGS_libnatpmp@ @TOR_LDFLAGS_libminiupnpc@
tor_fw_helper_LDADD = -lnatpmp -lminiupnpc ../../common/libor.a @TOR_LIB_WS32@
tor_fw_helper_CPPFLAGS = @TOR_CPPFLAGS_libnatpmp@ @TOR_CPPFLAGS_libminiupnpc@
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)
......@@ -2,51 +2,123 @@
* 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;
}
/** 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)
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;
int protocol = NATPMP_PROTOCOL_TCP;
int lease = NATPMP_DEFAULT_LEASE;
natpmp_t natpmp;
natpmpresp_t response;
fd_set fds;
struct timeval timeout;
if (tor_fw_options->verbose)
fprintf(stdout, "V: natpmp init...\n");
initnatpmp(&natpmp);
if (tor_fw_options->verbose)
fprintf(stdout, "V: sending natpmp portmapping request...\n");
r = sendnewportmappingrequest(&natpmp, protocol,
r = sendnewportmappingrequest(&(state->natpmp), state->protocol,
tor_fw_options->internal_port,
tor_fw_options->external_port,
lease);
fprintf(stdout, "tor-fw-helper: NAT-PMP sendnewportmappingrequest returned"
" %d (%s)\n", r, r==12?"SUCCESS":"FAILED");
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 {
FD_ZERO(&fds);
FD_SET(natpmp.s, &fds);
getnatpmprequesttimeout(&natpmp, &timeout);
select(FD_SETSIZE, &fds, NULL, NULL, &timeout);
FD_SET(state->natpmp.s, &fds);
getnatpmprequesttimeout(&(state->natpmp), &timeout);
x = select(FD_SETSIZE, &fds, NULL, NULL, &timeout);
if (x == -1)
{
fprintf(stdout, "V: select failed in "
"tor_natpmp_fetch_public_ip.\n");
return -1;
}
if (tor_fw_options->verbose)
fprintf(stdout, "V: attempting to readnatpmpreponseorretry...\n");
r = readnatpmpresponseorretry(&natpmp, &response);
r = readnatpmpresponseorretry(&(state->natpmp), &(state->response));
sav_errno = errno;
if (r<0 && r!=NATPMP_TRYAGAIN)
......@@ -58,48 +130,70 @@ tor_natpmp_add_tcp_mapping(tor_fw_options_t *tor_fw_options)
} 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",
response.pnu.newportmapping.mappedpublicport,
response.pnu.newportmapping.privateport,
response.pnu.newportmapping.lifetime);
(state->response).pnu.newportmapping.mappedpublicport,
(state->response).pnu.newportmapping.privateport,
(state->response).pnu.newportmapping.lifetime);
}
x = closenatpmp(&natpmp);
if (tor_fw_options->verbose)
fprintf(stdout, "V: closing natpmp socket: %d\n", x);
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)
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_t natpmp;
natpmpresp_t response;
natpmp_state_t *state = (natpmp_state_t *) backend_state;
struct timeval timeout;
fd_set fds;
r = initnatpmp(&natpmp);
if (tor_fw_options->verbose)
fprintf(stdout, "V: NAT-PMP init: %d\n", r);
r = sendpublicaddressrequest(&natpmp);
r = sendpublicaddressrequest(&(state->natpmp));
fprintf(stdout, "tor-fw-helper: NAT-PMP sendpublicaddressrequest returned"
" %d (%s)\n", r, r==2?"SUCCESS":"FAILED");
do {
if (state->natpmp.s >= FD_SETSIZE)
{
fprintf(stderr, "E: NAT-PMP FD_SETSIZE error %d\n",
state->natpmp.s);
return -1;
}
FD_ZERO(&fds);
FD_SET(natpmp.s, &fds);
getnatpmprequesttimeout(&natpmp, &timeout);
select(FD_SETSIZE, &fds, NULL, NULL, &timeout);
FD_SET(state->natpmp.s, &fds);
getnatpmprequesttimeout(&(state->natpmp), &timeout);
x = select(FD_SETSIZE, &fds, NULL, NULL, &timeout);
if (x == -1)
{
fprintf(stdout, "V: select failed in "
"tor_natpmp_fetch_public_ip.\n");
return -1;
}
if (tor_fw_options->verbose)
fprintf(stdout, "V: NAT-PMP attempting to read reponse...\n");
r = readnatpmpresponseorretry(&natpmp, &response);
r = readnatpmpresponseorretry(&(state->natpmp), &(state->response));
sav_errno = errno;
if (tor_fw_options->verbose)
......@@ -124,19 +218,18 @@ tor_natpmp_fetch_public_ip(tor_fw_options_t *tor_fw_options)
}
fprintf(stdout, "tor-fw-helper: ExternalIPAddress = %s\n",
inet_ntoa(response.pnu.publicaddress.addr));
x = closenatpmp(&natpmp);
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", response.type);
fprintf(stdout, "V: resultcode = %u\n", response.resultcode);
fprintf(stdout, "V: epoch = %u\n", response.epoch);
fprintf(stdout, "V: closing natpmp result: %d\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
......@@ -2,17 +2,46 @@
* 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
int tor_natpmp_add_tcp_mapping(tor_fw_options_t *tor_fw_options);
/** 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_fetch_public_ip(tor_fw_options_t *tor_fw_options);
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
......@@ -2,17 +2,31 @@
* 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 */
/** 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
......@@ -24,14 +38,42 @@
#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(miniupnpc_state_t *state)
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) {
......@@ -39,6 +81,7 @@ tor_upnp_init(miniupnpc_state_t *state)
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,
......@@ -53,9 +96,14 @@ tor_upnp_init(miniupnpc_state_t *state)
return UPNP_ERR_SUCCESS;
}
/** Tear down the UPnP connection stored in <b>backend_state</b>.*/
int
tor_upnp_cleanup(miniupnpc_state_t *state)
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;
......@@ -63,14 +111,17 @@ tor_upnp_cleanup(miniupnpc_state_t *state)
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(miniupnpc_state_t *state)
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(state);
r = tor_upnp_init(options, state);
if (r != UPNP_ERR_SUCCESS)
return r;
}
......@@ -84,34 +135,41 @@ tor_upnp_fetch_public_ip(miniupnpc_state_t *state)
if (externalIPAddress[0]) {
fprintf(stdout, "tor-fw-helper: ExternalIPAddress = %s\n",
externalIPAddress); tor_upnp_cleanup(state);
externalIPAddress); tor_upnp_cleanup(options, state);
options->public_ip_status = 1;
return UPNP_ERR_SUCCESS;
} else
goto err;
err:
tor_upnp_cleanup(state);
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(miniupnpc_state_t *state,
uint16_t internal_port, uint16_t external_port)
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];