tor-fw-helper-upnp.c 5 KB
Newer Older
1
2
3
4
/* Copyright (c) 2010, Jacob Appelbaum, Steven J. Murdoch.
 * Copyright (c) 2010, The Tor Project, Inc. */
/* See LICENSE for licensing information */

5
6
7
8
9
10
11
/**
  * \file tor-fw-helper-upnp.c
  * \brief The implementation of our UPnP firewall helper.
  **/

#include "orconfig.h"
#ifdef MINIUPNPC
12
13
14
15
#include <stdint.h>
#include <string.h>
#include <stdio.h>

16
17
18
#include <assert.h>

#include "compat.h"
19
20
21
#include "tor-fw-helper.h"
#include "tor-fw-helper-upnp.h"

22
/** UPnP timeout value. */
23
#define UPNP_DISCOVER_TIMEOUT 2000
24
/** Description of the port mapping in the UPnP table. */
25
26
#define UPNP_DESC "Tor relay"

27
28
29
/* 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. */
30
31
32
33
34
35
36
37
38
39
40
#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

41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/** 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>.*/
60
int
61
tor_upnp_init(tor_fw_options_t *options, void *backend_state)
62
{
63
64
65
66
67
68
69
70
  /*
    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;
71
72
73
74
75
  struct UPNPDev *devlist;
  int r;

  memset(&(state->urls), 0, sizeof(struct UPNPUrls));
  memset(&(state->data), 0, sizeof(struct IGDdatas));
76
  state->init = 0;
77
78
79
80
81
82
83

  devlist = upnpDiscover(UPNP_DISCOVER_TIMEOUT, NULL, NULL, 0);
  if (NULL == devlist) {
    fprintf(stderr, "E: upnpDiscover returned: NULL\n");
    return UPNP_ERR_NODEVICESFOUND;
  }

84
  assert(options);
85
86
87
88
89
90
91
92
93
94
95
96
97
98
  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;
}

99
/** Tear down the UPnP connection stored in <b>backend_state</b>.*/
100
int
101
tor_upnp_cleanup(tor_fw_options_t *options, void *backend_state)
102
{
103
104
105
106

  miniupnpc_state_t *state = (miniupnpc_state_t *) backend_state;
  assert(options);

107
108
109
110
111
112
113
  if (state->init)
    FreeUPNPUrls(&(state->urls));
  state->init = 0;

  return UPNP_ERR_SUCCESS;
}

114
115
/** Fetch our likely public IP from our upstream UPnP IGD enabled NAT device.
* Use the connection context stored in <b>backend_state</b>. */
116
int
117
tor_upnp_fetch_public_ip(tor_fw_options_t *options, void *backend_state)
118
{
119
  miniupnpc_state_t *state = (miniupnpc_state_t *) backend_state;
120
121
122
123
  int r;
  char externalIPAddress[16];

  if (!state->init) {
124
    r = tor_upnp_init(options, state);
125
126
127
128
129
130
131
132
133
134
135
136
137
    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",
138
139
            externalIPAddress); tor_upnp_cleanup(options, state);
    options->public_ip_status = 1;
140
141
142
143
144
    return UPNP_ERR_SUCCESS;
  } else
    goto err;

  err:
145
    tor_upnp_cleanup(options, state);
146
147
148
    return UPNP_ERR_GETEXTERNALIP;
}

149
150
/** 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>. */
151
int
152
tor_upnp_add_tcp_mapping(tor_fw_options_t *options, void *backend_state)
153
{
154
  miniupnpc_state_t *state = (miniupnpc_state_t *) backend_state;
155
156
157
158
159
  int r;
  char internal_port_str[6];
  char external_port_str[6];

  if (!state->init) {
160
    r = tor_upnp_init(options, state);
161
162
163
164
    if (r != UPNP_ERR_SUCCESS)
      return r;
  }

165
166
167
168
169
170
171
172
  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);
173
174
175
176
177
178
179
180

  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;

181
  options->upnp_status = 1;
182
183
  return UPNP_ERR_SUCCESS;
}
184
#endif
185