source: trunk/libtransmission/natpmp.c @ 9418

Last change on this file since 9418 was 9418, checked in by charles, 12 years ago

(trunk libT) fix #2534: if port forwarding is disabled, tr_sessionClose() closes a random file descriptor

  • Property svn:keywords set to Date Rev Author Id
File size: 6.6 KB
Line 
1/*
2 * This file Copyright (C) 2007-2009 Charles Kerr <charles@transmissionbt.com>
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: natpmp.c 9418 2009-10-27 20:29:02Z charles $
11 */
12
13#include <assert.h>
14#include <errno.h>
15#include <time.h>
16#include <inttypes.h>
17
18#define ENABLE_STRNATPMPERR
19#include <libnatpmp/natpmp.h>
20
21#include "transmission.h"
22#include "natpmp.h"
23#include "net.h" /* inet_ntoa() */
24#include "port-forwarding.h"
25#include "utils.h"
26
27#define LIFETIME_SECS 3600
28#define COMMAND_WAIT_SECS 8
29
30static const char *
31getKey( void ) { return _( "Port Forwarding (NAT-PMP)" ); }
32
33typedef enum
34{
35    TR_NATPMP_IDLE,
36    TR_NATPMP_ERR,
37    TR_NATPMP_DISCOVER,
38    TR_NATPMP_RECV_PUB,
39    TR_NATPMP_SEND_MAP,
40    TR_NATPMP_RECV_MAP,
41    TR_NATPMP_SEND_UNMAP,
42    TR_NATPMP_RECV_UNMAP
43}
44tr_natpmp_state;
45
46struct tr_natpmp
47{
48    tr_bool            isMapped;
49    tr_bool            hasDiscovered;
50    int                port;
51    time_t             renewTime;
52    time_t             commandTime;
53    tr_natpmp_state    state;
54    natpmp_t           natpmp;
55};
56
57/**
58***
59**/
60
61static void
62logVal( const char * func,
63        int          ret )
64{
65    if( ret == NATPMP_TRYAGAIN )
66        return;
67    if( ret >= 0 )
68        tr_ninf( getKey( ), _( "%s succeeded (%d)" ), func, ret );
69    else
70        tr_ndbg(
71             getKey( ),
72            "%s failed.  natpmp returned %d (%s); errno is %d (%s)",
73            func, ret, strnatpmperr( ret ), errno, tr_strerror( errno ) );
74}
75
76struct tr_natpmp*
77tr_natpmpInit( void )
78{
79    struct tr_natpmp * nat;
80
81    nat = tr_new0( struct tr_natpmp, 1 );
82    nat->state = TR_NATPMP_DISCOVER;
83    nat->port = -1;
84    nat->natpmp.s = -1; /* socket */
85    return nat;
86}
87
88void
89tr_natpmpClose( tr_natpmp * nat )
90{
91    if( nat )
92    {
93        if( nat->natpmp.s >= 0 )
94            tr_netCloseSocket( nat->natpmp.s );
95        tr_free( nat );
96    }
97}
98
99static int
100canSendCommand( const struct tr_natpmp * nat )
101{
102    return time( NULL ) >= nat->commandTime;
103}
104
105static void
106setCommandTime( struct tr_natpmp * nat )
107{
108    nat->commandTime = time( NULL ) + COMMAND_WAIT_SECS;
109}
110
111int
112tr_natpmpPulse( struct tr_natpmp * nat,
113                int                port,
114                int                isEnabled )
115{
116    int ret;
117
118    if( isEnabled && ( nat->state == TR_NATPMP_DISCOVER ) )
119    {
120        int val = initnatpmp( &nat->natpmp );
121        logVal( "initnatpmp", val );
122        val = sendpublicaddressrequest( &nat->natpmp );
123        logVal( "sendpublicaddressrequest", val );
124        nat->state = val < 0 ? TR_NATPMP_ERR : TR_NATPMP_RECV_PUB;
125        nat->hasDiscovered = 1;
126        setCommandTime( nat );
127    }
128
129    if( ( nat->state == TR_NATPMP_RECV_PUB ) && canSendCommand( nat ) )
130    {
131        natpmpresp_t response;
132        const int    val = readnatpmpresponseorretry( &nat->natpmp,
133                                                      &response );
134        logVal( "readnatpmpresponseorretry", val );
135        if( val >= 0 )
136        {
137            tr_ninf( getKey( ), _(
138                        "Found public address \"%s\"" ),
139                    inet_ntoa( response.pnu.publicaddress.addr ) );
140            nat->state = TR_NATPMP_IDLE;
141        }
142        else if( val != NATPMP_TRYAGAIN )
143        {
144            nat->state = TR_NATPMP_ERR;
145        }
146    }
147
148    if( ( nat->state == TR_NATPMP_IDLE ) || ( nat->state == TR_NATPMP_ERR ) )
149    {
150        if( nat->isMapped && ( !isEnabled || ( nat->port != port ) ) )
151            nat->state = TR_NATPMP_SEND_UNMAP;
152    }
153
154    if( ( nat->state == TR_NATPMP_SEND_UNMAP ) && canSendCommand( nat ) )
155    {
156        const int val =
157            sendnewportmappingrequest( &nat->natpmp, NATPMP_PROTOCOL_TCP,
158                                       nat->port, nat->port,
159                                       0 );
160        logVal( "sendnewportmappingrequest", val );
161        nat->state = val < 0 ? TR_NATPMP_ERR : TR_NATPMP_RECV_UNMAP;
162        setCommandTime( nat );
163    }
164
165    if( nat->state == TR_NATPMP_RECV_UNMAP )
166    {
167        natpmpresp_t resp;
168        const int    val = readnatpmpresponseorretry( &nat->natpmp, &resp );
169        logVal( "readnatpmpresponseorretry", val );
170        if( val >= 0 )
171        {
172            const int p = resp.pnu.newportmapping.privateport;
173            tr_ninf( getKey( ), _( "no longer forwarding port %d" ), p );
174            if( nat->port == p )
175            {
176                nat->port = -1;
177                nat->state = TR_NATPMP_IDLE;
178                nat->isMapped = 0;
179            }
180        }
181        else if( val != NATPMP_TRYAGAIN )
182        {
183            nat->state = TR_NATPMP_ERR;
184        }
185    }
186
187    if( nat->state == TR_NATPMP_IDLE )
188    {
189        if( isEnabled && !nat->isMapped && nat->hasDiscovered )
190            nat->state = TR_NATPMP_SEND_MAP;
191
192        else if( nat->isMapped && time( NULL ) >= nat->renewTime )
193            nat->state = TR_NATPMP_SEND_MAP;
194    }
195
196    if( ( nat->state == TR_NATPMP_SEND_MAP ) && canSendCommand( nat ) )
197    {
198        const int val =
199            sendnewportmappingrequest( &nat->natpmp, NATPMP_PROTOCOL_TCP,
200                                       port,
201                                       port,
202                                       LIFETIME_SECS );
203        logVal( "sendnewportmappingrequest", val );
204        nat->state = val < 0 ? TR_NATPMP_ERR : TR_NATPMP_RECV_MAP;
205        setCommandTime( nat );
206    }
207
208    if( nat->state == TR_NATPMP_RECV_MAP )
209    {
210        natpmpresp_t resp;
211        const int    val = readnatpmpresponseorretry( &nat->natpmp, &resp );
212        logVal( "readnatpmpresponseorretry", val );
213        if( val >= 0 )
214        {
215            nat->state = TR_NATPMP_IDLE;
216            nat->isMapped = 1;
217            nat->renewTime = time( NULL ) + LIFETIME_SECS;
218            nat->port = resp.pnu.newportmapping.privateport;
219            tr_ninf( getKey( ), _(
220                         "Port %d forwarded successfully" ), nat->port );
221        }
222        else if( val != NATPMP_TRYAGAIN )
223        {
224            nat->state = TR_NATPMP_ERR;
225        }
226    }
227
228    switch( nat->state )
229    {
230        case TR_NATPMP_IDLE:
231            ret = nat->isMapped ? TR_PORT_MAPPED : TR_PORT_UNMAPPED; break;
232
233        case TR_NATPMP_DISCOVER:
234            ret = TR_PORT_UNMAPPED; break;
235
236        case TR_NATPMP_RECV_PUB:
237        case TR_NATPMP_SEND_MAP:
238        case TR_NATPMP_RECV_MAP:
239            ret = TR_PORT_MAPPING; break;
240
241        case TR_NATPMP_SEND_UNMAP:
242        case TR_NATPMP_RECV_UNMAP:
243            ret = TR_PORT_UNMAPPING; break;
244
245        default:
246            ret = TR_PORT_ERROR; break;
247    }
248    return ret;
249}
250
Note: See TracBrowser for help on using the repository browser.