source: trunk/libtransmission/natpmp.c @ 10945

Last change on this file since 10945 was 10945, checked in by charles, 11 years ago

(trunk libT) #3383 "When port forwarding, check to see if the public port matches the private port" -- fixed

  • Property svn:keywords set to Date Rev Author Id
File size: 6.8 KB
Line 
1/*
2 * This file Copyright (C) 2007-2010 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 10945 2010-07-05 21:04:17Z 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           has_discovered;
48    tr_bool           is_mapped;
49
50    tr_port           public_port;
51    tr_port           private_port;
52
53    time_t            renew_time;
54    time_t            command_time;
55    tr_natpmp_state   state;
56    natpmp_t          natpmp;
57};
58
59/**
60***
61**/
62
63static void
64logVal( const char * func,
65        int          ret )
66{
67    if( ret == NATPMP_TRYAGAIN )
68        return;
69    if( ret >= 0 )
70        tr_ninf( getKey( ), _( "%s succeeded (%d)" ), func, ret );
71    else
72        tr_ndbg(
73             getKey( ),
74            "%s failed.  natpmp returned %d (%s); errno is %d (%s)",
75            func, ret, strnatpmperr( ret ), errno, tr_strerror( errno ) );
76}
77
78struct tr_natpmp*
79tr_natpmpInit( void )
80{
81    struct tr_natpmp * nat;
82
83    nat = tr_new0( struct tr_natpmp, 1 );
84    nat->state = TR_NATPMP_DISCOVER;
85    nat->public_port = 0;
86    nat->private_port = 0;
87    nat->natpmp.s = -1; /* socket */
88    return nat;
89}
90
91void
92tr_natpmpClose( tr_natpmp * nat )
93{
94    if( nat )
95    {
96        if( nat->natpmp.s >= 0 )
97            tr_netCloseSocket( nat->natpmp.s );
98        tr_free( nat );
99    }
100}
101
102static int
103canSendCommand( const struct tr_natpmp * nat )
104{
105    return tr_time( ) >= nat->command_time;
106}
107
108static void
109setCommandTime( struct tr_natpmp * nat )
110{
111    nat->command_time = tr_time( ) + COMMAND_WAIT_SECS;
112}
113
114int
115tr_natpmpPulse( struct tr_natpmp * nat, tr_port private_port, tr_bool is_enabled, tr_port * public_port )
116{
117    int ret;
118
119    if( is_enabled && ( nat->state == TR_NATPMP_DISCOVER ) )
120    {
121        int val = initnatpmp( &nat->natpmp );
122        logVal( "initnatpmp", val );
123        val = sendpublicaddressrequest( &nat->natpmp );
124        logVal( "sendpublicaddressrequest", val );
125        nat->state = val < 0 ? TR_NATPMP_ERR : TR_NATPMP_RECV_PUB;
126        nat->has_discovered = TRUE;
127        setCommandTime( nat );
128    }
129
130    if( ( nat->state == TR_NATPMP_RECV_PUB ) && canSendCommand( nat ) )
131    {
132        natpmpresp_t response;
133        const int val = readnatpmpresponseorretry( &nat->natpmp, &response );
134        logVal( "readnatpmpresponseorretry", val );
135        if( val >= 0 )
136        {
137            tr_ninf( getKey( ), _( "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->is_mapped && ( !is_enabled || ( nat->private_port != private_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 = sendnewportmappingrequest( &nat->natpmp, NATPMP_PROTOCOL_TCP,
156                                                   nat->private_port,
157                                                   nat->public_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 private_port = resp.pnu.newportmapping.privateport;
172
173            tr_ninf( getKey( ), _( "no longer forwarding port %d" ), private_port );
174
175            if( nat->private_port == private_port )
176            {
177                nat->private_port = 0;
178                nat->public_port = 0;
179                nat->state = TR_NATPMP_IDLE;
180                nat->is_mapped = FALSE;
181            }
182        }
183        else if( val != NATPMP_TRYAGAIN )
184        {
185            nat->state = TR_NATPMP_ERR;
186        }
187    }
188
189    if( nat->state == TR_NATPMP_IDLE )
190    {
191        if( is_enabled && !nat->is_mapped && nat->has_discovered )
192            nat->state = TR_NATPMP_SEND_MAP;
193
194        else if( nat->is_mapped && tr_time( ) >= nat->renew_time )
195            nat->state = TR_NATPMP_SEND_MAP;
196    }
197
198    if( ( nat->state == TR_NATPMP_SEND_MAP ) && canSendCommand( nat ) )
199    {
200        const int val = sendnewportmappingrequest( &nat->natpmp, NATPMP_PROTOCOL_TCP, private_port, private_port, 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->is_mapped = TRUE;
215            nat->renew_time = tr_time( ) + LIFETIME_SECS;
216            nat->private_port = resp.pnu.newportmapping.privateport;
217            nat->public_port = resp.pnu.newportmapping.mappedpublicport;
218            tr_ninf( getKey( ), _( "Port %d forwarded successfully" ), nat->private_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            *public_port = nat->public_port;
230            return nat->is_mapped ? TR_PORT_MAPPED : TR_PORT_UNMAPPED;
231            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.