source: trunk/libtransmission/natpmp.c @ 9846

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

(trunk libT) fix <assert.h> #includes

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