source: trunk/libtransmission/upnp.c @ 13868

Last change on this file since 13868 was 13868, checked in by jordan, 9 years ago

make all the log functions/structs/enums use a single 'tr_log' namespace, such as tr_logGetQueue, tr_logAddInfo, tr_logIsLevelActive

  • Property svn:keywords set to Date Rev Author Id
File size: 8.2 KB
Line 
1/*
2 * This file Copyright (C) Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2. Works owned by the
5 * Transmission project are granted a special exemption to clause 2 (b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: upnp.c 13868 2013-01-25 23:34:20Z jordan $
11 */
12
13#include <assert.h>
14#include <errno.h>
15
16#ifdef SYSTEM_MINIUPNP
17  #include <miniupnpc/miniupnpc.h>
18  #include <miniupnpc/upnpcommands.h>
19#else
20  #include <miniupnp/miniupnpc.h>
21  #include <miniupnp/upnpcommands.h>
22#endif
23
24#ifdef SYS_DARWIN
25  #define HAVE_MINIUPNP_16 1
26#endif
27
28#include "transmission.h"
29#include "log.h"
30#include "port-forwarding.h"
31#include "session.h"
32#include "upnp.h"
33#include "utils.h"
34
35static const char *
36getKey (void) { return _("Port Forwarding (UPnP)"); }
37
38typedef enum
39{
40    TR_UPNP_IDLE,
41    TR_UPNP_ERR,
42    TR_UPNP_DISCOVER,
43    TR_UPNP_MAP,
44    TR_UPNP_UNMAP
45}
46tr_upnp_state;
47
48struct tr_upnp
49{
50    bool               hasDiscovered;
51    struct UPNPUrls    urls;
52    struct IGDdatas    data;
53    int                port;
54    char               lanaddr[16];
55    unsigned int       isMapped;
56    tr_upnp_state      state;
57};
58
59/**
60***
61**/
62
63tr_upnp*
64tr_upnpInit (void)
65{
66    tr_upnp * ret = tr_new0 (tr_upnp, 1);
67
68    ret->state = TR_UPNP_DISCOVER;
69    ret->port = -1;
70    return ret;
71}
72
73void
74tr_upnpClose (tr_upnp * handle)
75{
76    assert (!handle->isMapped);
77    assert ((handle->state == TR_UPNP_IDLE)
78          || (handle->state == TR_UPNP_ERR)
79          || (handle->state == TR_UPNP_DISCOVER));
80
81    if (handle->hasDiscovered)
82        FreeUPNPUrls (&handle->urls);
83    tr_free (handle);
84}
85
86/**
87***  Wrappers for miniupnpc functions
88**/
89
90static struct UPNPDev *
91tr_upnpDiscover (int msec)
92{
93    int err = 0;
94    struct UPNPDev * ret = NULL;
95
96#if defined (HAVE_MINIUPNP_16)
97    ret = upnpDiscover (msec, NULL, NULL, 0, 0, &err);
98#elif defined (HAVE_MINIUPNP_15)
99    ret = upnpDiscover (msec, NULL, NULL, 0);
100#else
101    ret = UPNPCOMMAND_UNKNOWN_ERROR;
102#endif
103
104    if (ret != UPNPCOMMAND_SUCCESS)
105        tr_logAddNamedDbg (getKey (), "upnpDiscover failed (errno %d - %s)", err, tr_strerror (err));
106
107    return ret;
108}
109
110static int
111tr_upnpGetSpecificPortMappingEntry (tr_upnp * handle, const char * proto)
112{
113    int err;
114    char intClient[16];
115    char intPort[16];
116    char portStr[16];
117
118    *intClient = '\0';
119    *intPort = '\0';
120
121    tr_snprintf (portStr, sizeof (portStr), "%d", (int)handle->port);
122
123#if defined (HAVE_MINIUPNP_16)
124    err = UPNP_GetSpecificPortMappingEntry (handle->urls.controlURL, handle->data.first.servicetype, portStr, proto, intClient, intPort, NULL, NULL, NULL);
125#elif defined (HAVE_MINIUPNP_15)
126    err = UPNP_GetSpecificPortMappingEntry (handle->urls.controlURL, handle->data.first.servicetype, portStr, proto, intClient, intPort);
127#else
128    err = UPNPCOMMAND_UNKNOWN_ERROR;
129#endif
130
131    return err;
132}
133
134static int
135tr_upnpAddPortMapping (const tr_upnp * handle, const char * proto, tr_port port, const char * desc)
136{
137    int err;
138    const int old_errno = errno;
139    char portStr[16];
140    errno = 0;
141
142    tr_snprintf (portStr, sizeof (portStr), "%d", (int)port);
143
144#if defined (HAVE_MINIUPNP_16)
145    err = UPNP_AddPortMapping (handle->urls.controlURL, handle->data.first.servicetype, portStr, portStr, handle->lanaddr, desc, proto, NULL, NULL);
146#elif defined (HAVE_MINIUPNP_15)
147    err = UPNP_AddPortMapping (handle->urls.controlURL, handle->data.first.servicetype, portStr, portStr, handle->lanaddr, desc, proto, NULL);
148#else
149    err = UPNPCOMMAND_UNKNOWN_ERROR;
150#endif
151
152    if (err)
153        tr_logAddNamedDbg (getKey (), "%s Port forwarding failed with error %d (errno %d - %s)", proto, err, errno, tr_strerror (errno));
154
155    errno = old_errno;
156    return err;
157}
158
159static void
160tr_upnpDeletePortMapping (const tr_upnp * handle, const char * proto, tr_port port)
161{
162    char portStr[16];
163
164    tr_snprintf (portStr, sizeof (portStr), "%d", (int)port);
165
166    UPNP_DeletePortMapping (handle->urls.controlURL,
167                            handle->data.first.servicetype,
168                            portStr, proto, NULL);
169}
170
171/**
172***
173**/
174
175enum
176{
177  UPNP_IGD_NONE = 0,
178  UPNP_IGD_VALID_CONNECTED = 1,
179  UPNP_IGD_VALID_NOT_CONNECTED = 2,
180  UPNP_IGD_INVALID = 3
181};
182
183int
184tr_upnpPulse (tr_upnp * handle,
185              int       port,
186              int       isEnabled,
187              int       doPortCheck)
188{
189    int ret;
190
191    if (isEnabled && (handle->state == TR_UPNP_DISCOVER))
192    {
193        struct UPNPDev * devlist;
194
195        devlist = tr_upnpDiscover (2000);
196
197        errno = 0;
198        if (UPNP_GetValidIGD (devlist, &handle->urls, &handle->data,
199                             handle->lanaddr, sizeof (handle->lanaddr)) == UPNP_IGD_VALID_CONNECTED)
200        {
201            tr_logAddNamedInfo (getKey (), _(
202                         "Found Internet Gateway Device \"%s\""),
203                     handle->urls.controlURL);
204            tr_logAddNamedInfo (getKey (), _(
205                         "Local Address is \"%s\""), handle->lanaddr);
206            handle->state = TR_UPNP_IDLE;
207            handle->hasDiscovered = 1;
208        }
209        else
210        {
211            handle->state = TR_UPNP_ERR;
212            tr_logAddNamedDbg (
213                 getKey (), "UPNP_GetValidIGD failed (errno %d - %s)",
214                errno,
215                tr_strerror (errno));
216            tr_logAddNamedDbg (
217                getKey (),
218                "If your router supports UPnP, please make sure UPnP is enabled!");
219        }
220        freeUPNPDevlist (devlist);
221    }
222
223    if (handle->state == TR_UPNP_IDLE)
224    {
225        if (handle->isMapped && (!isEnabled || (handle->port != port)))
226            handle->state = TR_UPNP_UNMAP;
227    }
228
229    if (isEnabled && handle->isMapped && doPortCheck)
230    {
231        if ((tr_upnpGetSpecificPortMappingEntry (handle, "TCP") != UPNPCOMMAND_SUCCESS) ||
232          (tr_upnpGetSpecificPortMappingEntry (handle, "UDP") != UPNPCOMMAND_SUCCESS))
233        {
234            tr_logAddNamedInfo (getKey (), _("Port %d isn't forwarded"), handle->port);
235            handle->isMapped = false;
236        }
237    }
238
239    if (handle->state == TR_UPNP_UNMAP)
240    {
241        tr_upnpDeletePortMapping (handle, "TCP", handle->port);
242        tr_upnpDeletePortMapping (handle, "UDP", handle->port);
243
244        tr_logAddNamedInfo (getKey (),
245                 _("Stopping port forwarding through \"%s\", service \"%s\""),
246                 handle->urls.controlURL, handle->data.first.servicetype);
247
248        handle->isMapped = 0;
249        handle->state = TR_UPNP_IDLE;
250        handle->port = -1;
251    }
252
253    if (handle->state == TR_UPNP_IDLE)
254    {
255        if (isEnabled && !handle->isMapped)
256            handle->state = TR_UPNP_MAP;
257    }
258
259    if (handle->state == TR_UPNP_MAP)
260    {
261        int  err_tcp = -1;
262        int  err_udp = -1;
263        errno = 0;
264
265        if (!handle->urls.controlURL || !handle->data.first.servicetype)
266            handle->isMapped = 0;
267        else
268        {
269            char desc[64];
270            tr_snprintf (desc, sizeof (desc), "%s at %d", TR_NAME, port);
271
272            err_tcp = tr_upnpAddPortMapping (handle, "TCP", port, desc);
273            err_udp = tr_upnpAddPortMapping (handle, "UDP", port, desc);
274
275            handle->isMapped = !err_tcp | !err_udp;
276        }
277        tr_logAddNamedInfo (getKey (),
278                 _("Port forwarding through \"%s\", service \"%s\". (local address: %s:%d)"),
279                 handle->urls.controlURL, handle->data.first.servicetype,
280                 handle->lanaddr, port);
281        if (handle->isMapped)
282        {
283            tr_logAddNamedInfo (getKey (), "%s", _("Port forwarding successful!"));
284            handle->port = port;
285            handle->state = TR_UPNP_IDLE;
286        }
287        else
288        {
289            tr_logAddNamedDbg (getKey (), "If your router supports UPnP, please make sure UPnP is enabled!");
290            handle->port = -1;
291            handle->state = TR_UPNP_ERR;
292        }
293    }
294
295    switch (handle->state)
296    {
297        case TR_UPNP_DISCOVER:
298            ret = TR_PORT_UNMAPPED; break;
299
300        case TR_UPNP_MAP:
301            ret = TR_PORT_MAPPING; break;
302
303        case TR_UPNP_UNMAP:
304            ret = TR_PORT_UNMAPPING; break;
305
306        case TR_UPNP_IDLE:
307            ret = handle->isMapped ? TR_PORT_MAPPED
308                  : TR_PORT_UNMAPPED; break;
309
310        default:
311            ret = TR_PORT_ERROR; break;
312    }
313
314    return ret;
315}
316
Note: See TracBrowser for help on using the repository browser.