source: trunk/libtransmission/upnp.c @ 12957

Last change on this file since 12957 was 12957, checked in by jordan, 11 years ago

(trunk libT) #4323 "Allow usage of system miniupnpc" -- fixed.

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