source: trunk/libtransmission/natpmp.c @ 4176

Last change on this file since 4176 was 4176, checked in by charles, 14 years ago

try to make the natpmp error messages yet more helpful. thanks to m1b and Lacrocivious

  • Property svn:keywords set to Date Rev Author Id
File size: 6.2 KB
Line 
1/*
2 * This file Copyright (C) 2007 Charles Kerr <charles@rebelbase.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 4176 2007-12-15 06:10:24Z charles $
11 */
12
13#include <assert.h>
14#include <errno.h>
15#include <time.h>
16#include <inttypes.h>
17#include <string.h> /* strerror */
18
19#include <sys/socket.h>
20#include <netinet/in.h>
21#include <arpa/inet.h>
22
23#include <libnatpmp/natpmp.h>
24
25#include "transmission.h"
26#include "natpmp.h"
27#include "shared.h"
28#include "utils.h"
29
30#define LIFETIME_SECS 3600
31#define COMMAND_WAIT_SECS 8
32
33#define KEY "Port Mapping (NAT-PMP): "
34
35typedef enum
36{
37    TR_NATPMP_IDLE,
38    TR_NATPMP_ERR,
39    TR_NATPMP_DISCOVER,
40    TR_NATPMP_RECV_PUB,
41    TR_NATPMP_SEND_MAP,
42    TR_NATPMP_RECV_MAP,
43    TR_NATPMP_SEND_UNMAP,
44    TR_NATPMP_RECV_UNMAP
45}
46tr_natpmp_state;
47   
48struct tr_natpmp
49{
50    int port;
51    unsigned int isMapped      : 1;
52    unsigned int hasDiscovered : 1;
53    time_t renewTime;
54    time_t commandTime;
55    tr_natpmp_state state;
56    natpmp_t natpmp;
57};
58
59/**
60***
61**/
62
63static void
64logVal( const char * func, int ret )
65{
66    if( ret==NATPMP_TRYAGAIN )
67        tr_dbg( KEY "%s returned 'try again'", func );
68    else if( ret >= 0 )
69        tr_dbg( KEY "%s returned success (%d)", func, ret );
70    else
71        tr_err( KEY "%s returned error %d, errno is %d (%s)", func, ret, errno, strerror(errno) );
72}
73
74struct tr_natpmp*
75tr_natpmpInit( void )
76{
77    struct tr_natpmp * nat;
78    nat = tr_new0( struct tr_natpmp, 1 );
79    nat->state = TR_NATPMP_DISCOVER;
80    nat->port = -1;
81    return nat;
82}
83
84void
85tr_natpmpClose( tr_natpmp * nat )
86{
87    assert( !nat->isMapped );
88    assert( ( nat->state == TR_NATPMP_IDLE )
89         || ( nat->state == TR_NATPMP_ERR )
90         || ( nat->state == TR_NATPMP_DISCOVER ) );
91
92    closenatpmp( &nat->natpmp );
93    tr_free( nat );
94}
95
96static int
97canSendCommand( const struct tr_natpmp * nat )
98{
99    return time(NULL) >= nat->commandTime;
100}
101
102static void
103setCommandTime( struct tr_natpmp * nat )
104{
105    nat->commandTime = time(NULL) + COMMAND_WAIT_SECS;
106}
107
108static void
109setErrorState( struct tr_natpmp * nat )
110{
111    tr_err( KEY "If your router supports NAT-PMP, please make sure NAT-PMP is enabled!" );
112    tr_err( KEY "NAT-PMP port forwarding unsuccessful, trying UPnP next" );
113    nat->state = TR_NATPMP_ERR;
114}
115
116int
117tr_natpmpPulse( struct tr_natpmp * nat, int port, int isEnabled )
118{
119    int ret;
120
121    if( isEnabled && ( nat->state == TR_NATPMP_DISCOVER ) )
122    {
123        int val = initnatpmp( &nat->natpmp );
124        logVal( "initnatpmp", val );
125        val = sendpublicaddressrequest( &nat->natpmp );
126        logVal( "sendpublicaddressrequest", val );
127        nat->state = val < 0 ? TR_NATPMP_ERR : TR_NATPMP_RECV_PUB;
128        nat->hasDiscovered = 1;
129        setCommandTime( nat );
130    }
131
132    if( ( nat->state == TR_NATPMP_RECV_PUB ) && canSendCommand( nat ) )
133    {
134        natpmpresp_t response;
135        const int val = readnatpmpresponseorretry( &nat->natpmp, &response );
136        logVal( "readnatpmpresponseorretry", val );
137        if( val >= 0 ) {
138            tr_inf( KEY "found public address %s", inet_ntoa( response.publicaddress.addr ) );
139            nat->state = TR_NATPMP_IDLE;
140        } else if( val != NATPMP_TRYAGAIN ) {
141            setErrorState( nat );
142        }
143    }
144
145    if( ( nat->state == TR_NATPMP_IDLE ) || ( nat->state == TR_NATPMP_ERR ) )
146    {
147        if( nat->isMapped && ( !isEnabled || ( nat->port != port ) ) )
148            nat->state = TR_NATPMP_SEND_UNMAP;
149    }
150
151    if( ( nat->state == TR_NATPMP_SEND_UNMAP ) && canSendCommand( nat ) )
152    {
153        const int val = sendnewportmappingrequest( &nat->natpmp, NATPMP_PROTOCOL_TCP, nat->port, nat->port, 0 );
154        logVal( "sendnewportmappingrequest", val );
155        nat->state = val < 0 ? TR_NATPMP_ERR : TR_NATPMP_RECV_UNMAP;
156        setCommandTime( nat );
157    }
158
159    if( nat->state == TR_NATPMP_RECV_UNMAP )
160    {
161        natpmpresp_t resp;
162        const int val = readnatpmpresponseorretry( &nat->natpmp, &resp );
163        logVal( "readnatpmpresponseorretry", val );
164        if( val >= 0 ) {
165            tr_inf( KEY "port %d has been unmapped.", nat->port );
166            nat->state = TR_NATPMP_IDLE;
167            nat->port = -1;
168            nat->isMapped = 0;
169        } else if( val != NATPMP_TRYAGAIN ) {
170            setErrorState( nat );
171        }
172    }
173
174    if( nat->state == TR_NATPMP_IDLE )
175    {
176        if( isEnabled && !nat->isMapped && nat->hasDiscovered )
177            nat->state = TR_NATPMP_SEND_MAP;
178
179        else if( nat->isMapped && time(NULL) >= nat->renewTime )
180            nat->state = TR_NATPMP_SEND_MAP;
181    }
182
183    if( ( nat->state == TR_NATPMP_SEND_MAP ) && canSendCommand( nat ) )
184    {
185        const int val = sendnewportmappingrequest( &nat->natpmp, NATPMP_PROTOCOL_TCP, port, port, LIFETIME_SECS );
186        logVal( "sendnewportmappingrequest", val );
187        nat->state = val < 0 ? TR_NATPMP_ERR : TR_NATPMP_RECV_MAP;
188        setCommandTime( nat );
189    }
190
191    if( nat->state == TR_NATPMP_RECV_MAP )
192    {
193        natpmpresp_t resp;
194        const int val = readnatpmpresponseorretry( &nat->natpmp, &resp );
195        logVal( "readnatpmpresponseorretry", val );
196        if( val >= 0 ) {
197            nat->state = TR_NATPMP_IDLE;
198            nat->isMapped = 1;
199            nat->renewTime = time( NULL ) + LIFETIME_SECS;
200            nat->port = resp.newportmapping.privateport;
201            tr_inf( KEY "port %d mapped successfully", nat->port );
202        } else if( val != NATPMP_TRYAGAIN ) {
203            setErrorState( nat );
204        }
205    }
206
207    switch( nat->state ) {
208        case TR_NATPMP_IDLE:        ret = nat->isMapped ? TR_NAT_TRAVERSAL_MAPPED : TR_NAT_TRAVERSAL_UNMAPPED; break;
209        case TR_NATPMP_DISCOVER:    ret = TR_NAT_TRAVERSAL_UNMAPPED; break;
210        case TR_NATPMP_RECV_PUB:
211        case TR_NATPMP_SEND_MAP:
212        case TR_NATPMP_RECV_MAP:    ret = TR_NAT_TRAVERSAL_MAPPING; break;
213        case TR_NATPMP_SEND_UNMAP:
214        case TR_NATPMP_RECV_UNMAP:  ret = TR_NAT_TRAVERSAL_UNMAPPING; break;
215        default:                    ret = TR_NAT_TRAVERSAL_ERROR; break;
216    }
217    return ret;
218}
Note: See TracBrowser for help on using the repository browser.