source: trunk/libtransmission/natpmp.c @ 7658

Last change on this file since 7658 was 7658, checked in by charles, 13 years ago

(trunk) update the GPL code's copyright dates

  • 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 7658 2009-01-10 23:09:07Z 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    return nat;
85}
86
87void
88tr_natpmpClose( tr_natpmp * nat )
89{
90    if( nat )
91    {
92        closenatpmp( &nat->natpmp );
93        tr_free( nat );
94    }
95}
96
97static int
98canSendCommand( const struct tr_natpmp * nat )
99{
100    return time( NULL ) >= nat->commandTime;
101}
102
103static void
104setCommandTime( struct tr_natpmp * nat )
105{
106    nat->commandTime = time( NULL ) + COMMAND_WAIT_SECS;
107}
108
109int
110tr_natpmpPulse( struct tr_natpmp * nat,
111                int                port,
112                int                isEnabled )
113{
114    int ret;
115
116    if( isEnabled && ( nat->state == TR_NATPMP_DISCOVER ) )
117    {
118        int val = initnatpmp( &nat->natpmp );
119        logVal( "initnatpmp", val );
120        val = sendpublicaddressrequest( &nat->natpmp );
121        logVal( "sendpublicaddressrequest", val );
122        nat->state = val < 0 ? TR_NATPMP_ERR : TR_NATPMP_RECV_PUB;
123        nat->hasDiscovered = 1;
124        setCommandTime( nat );
125    }
126
127    if( ( nat->state == TR_NATPMP_RECV_PUB ) && canSendCommand( nat ) )
128    {
129        natpmpresp_t response;
130        const int    val = readnatpmpresponseorretry( &nat->natpmp,
131                                                      &response );
132        logVal( "readnatpmpresponseorretry", val );
133        if( val >= 0 )
134        {
135            tr_ninf( getKey( ), _(
136                        "Found public address \"%s\"" ),
137                    inet_ntoa( response.pnu.publicaddress.addr ) );
138            nat->state = TR_NATPMP_IDLE;
139        }
140        else if( val != NATPMP_TRYAGAIN )
141        {
142            nat->state = TR_NATPMP_ERR;
143        }
144    }
145
146    if( ( nat->state == TR_NATPMP_IDLE ) || ( nat->state == TR_NATPMP_ERR ) )
147    {
148        if( nat->isMapped && ( !isEnabled || ( nat->port != port ) ) )
149            nat->state = TR_NATPMP_SEND_UNMAP;
150    }
151
152    if( ( nat->state == TR_NATPMP_SEND_UNMAP ) && canSendCommand( nat ) )
153    {
154        const int val =
155            sendnewportmappingrequest( &nat->natpmp, NATPMP_PROTOCOL_TCP,
156                                       nat->port, nat->port,
157                                       0 );
158        logVal( "sendnewportmappingrequest", val );
159        nat->state = val < 0 ? TR_NATPMP_ERR : TR_NATPMP_RECV_UNMAP;
160        setCommandTime( nat );
161    }
162
163    if( nat->state == TR_NATPMP_RECV_UNMAP )
164    {
165        natpmpresp_t resp;
166        const int    val = readnatpmpresponseorretry( &nat->natpmp, &resp );
167        logVal( "readnatpmpresponseorretry", val );
168        if( val >= 0 )
169        {
170            const int p = resp.pnu.newportmapping.privateport;
171            tr_ninf( getKey( ), _( "no longer forwarding port %d" ), p );
172            if( nat->port == p )
173            {
174                nat->port = -1;
175                nat->state = TR_NATPMP_IDLE;
176                nat->isMapped = 0;
177            }
178        }
179        else if( val != NATPMP_TRYAGAIN )
180        {
181            nat->state = TR_NATPMP_ERR;
182        }
183    }
184
185    if( nat->state == TR_NATPMP_IDLE )
186    {
187        if( isEnabled && !nat->isMapped && nat->hasDiscovered )
188            nat->state = TR_NATPMP_SEND_MAP;
189
190        else if( nat->isMapped && time( NULL ) >= nat->renewTime )
191            nat->state = TR_NATPMP_SEND_MAP;
192    }
193
194    if( ( nat->state == TR_NATPMP_SEND_MAP ) && canSendCommand( nat ) )
195    {
196        const int val =
197            sendnewportmappingrequest( &nat->natpmp, NATPMP_PROTOCOL_TCP,
198                                       port,
199                                       port,
200                                       LIFETIME_SECS );
201        logVal( "sendnewportmappingrequest", val );
202        nat->state = val < 0 ? TR_NATPMP_ERR : TR_NATPMP_RECV_MAP;
203        setCommandTime( nat );
204    }
205
206    if( nat->state == TR_NATPMP_RECV_MAP )
207    {
208        natpmpresp_t resp;
209        const int    val = readnatpmpresponseorretry( &nat->natpmp, &resp );
210        logVal( "readnatpmpresponseorretry", val );
211        if( val >= 0 )
212        {
213            nat->state = TR_NATPMP_IDLE;
214            nat->isMapped = 1;
215            nat->renewTime = time( NULL ) + LIFETIME_SECS;
216            nat->port = resp.pnu.newportmapping.privateport;
217            tr_ninf( getKey( ), _(
218                         "Port %d forwarded successfully" ), nat->port );
219        }
220        else if( val != NATPMP_TRYAGAIN )
221        {
222            nat->state = TR_NATPMP_ERR;
223        }
224    }
225
226    switch( nat->state )
227    {
228        case TR_NATPMP_IDLE:
229            ret = nat->isMapped ? TR_PORT_MAPPED : TR_PORT_UNMAPPED; break;
230
231        case TR_NATPMP_DISCOVER:
232            ret = TR_PORT_UNMAPPED; break;
233
234        case TR_NATPMP_RECV_PUB:
235        case TR_NATPMP_SEND_MAP:
236        case TR_NATPMP_RECV_MAP:
237            ret = TR_PORT_MAPPING; break;
238
239        case TR_NATPMP_SEND_UNMAP:
240        case TR_NATPMP_RECV_UNMAP:
241            ret = TR_PORT_UNMAPPING; break;
242
243        default:
244            ret = TR_PORT_ERROR; break;
245    }
246    return ret;
247}
248
Note: See TracBrowser for help on using the repository browser.