source: trunk/libtransmission/port-forwarding.c @ 7397

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

(trunk libT) add ipv6 support by jhujhiti. I think this is the largest user-contributed patch we've ever used... thanks jhujhiti :)

  • Property svn:keywords set to Date Rev Author Id
File size: 8.1 KB
Line 
1/*
2 * This file Copyright (C) 2008 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: port-forwarding.c 7397 2008-12-15 00:17:08Z charles $
11 */
12
13#include <errno.h>
14#include <string.h>
15#include <stdio.h>
16
17#include <sys/types.h>
18
19#include "transmission.h"
20#include "natpmp.h"
21#include "net.h"
22#include "peer-io.h"
23#include "peer-mgr.h"
24#include "port-forwarding.h"
25#include "torrent.h"
26#include "trevent.h"
27#include "upnp.h"
28#include "utils.h"
29
30static const char *
31getKey( void ) { return _( "Port Forwarding" ); }
32
33struct tr_shared
34{
35    tr_bool               isEnabled;
36    tr_bool               isShuttingDown;
37
38    tr_port_forwarding    natpmpStatus;
39    tr_port_forwarding    upnpStatus;
40
41    tr_bool               shouldChange;
42    tr_socketList       * bindSockets;
43    tr_port               publicPort;
44
45    tr_timer            * pulseTimer;
46
47    tr_upnp             * upnp;
48    tr_natpmp           * natpmp;
49    tr_session          * session;
50};
51
52/***
53****
54***/
55
56static const char*
57getNatStateStr( int state )
58{
59    switch( state )
60    {
61        /* we're in the process of trying to set up port forwarding */
62        case TR_PORT_MAPPING:
63            return _( "Starting" );
64
65        /* we've successfully forwarded the port */
66        case TR_PORT_MAPPED:
67            return _( "Forwarded" );
68
69        /* we're cancelling the port forwarding */
70        case TR_PORT_UNMAPPING:
71            return _( "Stopping" );
72
73        /* the port isn't forwarded */
74        case TR_PORT_UNMAPPED:
75            return _( "Not forwarded" );
76
77        case TR_PORT_ERROR:
78            return "???";
79    }
80
81    return "notfound";
82}
83
84static void
85natPulse( tr_shared * s )
86{
87    const tr_port port = s->publicPort;
88    const int     isEnabled = s->isEnabled && !s->isShuttingDown;
89    int           oldStatus;
90    int           newStatus;
91
92    oldStatus = tr_sharedTraversalStatus( s );
93    s->natpmpStatus = tr_natpmpPulse( s->natpmp, port, isEnabled );
94    s->upnpStatus = tr_upnpPulse( s->upnp, port, isEnabled );
95    newStatus = tr_sharedTraversalStatus( s );
96
97    if( newStatus != oldStatus )
98        tr_ninf( getKey( ), _( "State changed from \"%1$s\" to \"%2$s\"" ),
99                getNatStateStr( oldStatus ),
100                getNatStateStr( newStatus ) );
101}
102
103/*
104 * Callbacks for socket list
105 */
106static void
107closeCb( int * const socket,
108         tr_address * const addr,
109         void * const userData )
110{
111    tr_shared * s = ( tr_shared * )userData;
112    if( *socket >= 0 )
113    {
114        tr_ninf( getKey( ), _( "Closing port %d on %s" ), s->publicPort,
115                tr_ntop_non_ts( addr ) );
116        tr_netClose( *socket );
117    }
118}
119
120static void
121acceptCb( int * const socket,
122          tr_address * const addr,
123          void * const userData )
124{
125    tr_shared * s = ( tr_shared * )userData;
126    tr_address clientAddr;
127    tr_port clientPort;
128    int clientSocket;
129    clientSocket = tr_netAccept( s->session, *socket, &clientAddr, &clientPort );
130    if( clientSocket > 0 )
131    {
132        tr_deepLog( __FILE__, __LINE__, NULL,
133                   "New INCOMING connection %d (%s)",
134                   clientSocket, tr_peerIoAddrStr( &clientAddr, clientPort ) );
135       
136        tr_peerMgrAddIncoming( s->session->peerMgr, &clientAddr, clientPort,
137                              clientSocket );
138    }
139}
140
141static void
142bindCb( int * const socket,
143        tr_address * const addr,
144        void * const userData )
145{
146    tr_shared * s = ( tr_shared * )userData;
147    *socket = tr_netBindTCP( addr, s->publicPort, FALSE );
148    if( *socket >= 0 )
149    {
150        tr_ninf( getKey( ),
151                _( "Opened port %d on %s to listen for incoming peer connections" ),
152                s->publicPort, tr_ntop_non_ts( addr ) );
153        listen( *socket, 10 );
154    }
155    else
156    {
157        tr_nerr( getKey( ),
158                _(
159                  "Couldn't open port %d on %s to listen for incoming peer connections (errno %d - %s)" ),
160                s->publicPort, tr_ntop_non_ts( addr ), errno, tr_strerror( errno ) );
161    }
162}
163
164static void
165incomingPeersPulse( tr_shared * s )
166{
167    int allPaused;
168    tr_torrent * tor;
169   
170    if( s->shouldChange )
171    {
172        tr_socketListForEach( s->bindSockets, &closeCb, s );
173        s->shouldChange = FALSE;
174        if( s->publicPort > 0 )
175            tr_socketListForEach( s->bindSockets, &bindCb, s );
176    }
177   
178    /* see if any torrents aren't paused */
179    allPaused = 1;
180    tor = NULL;
181    while( ( tor = tr_torrentNext( s->session, tor ) ) )
182    {
183        if( TR_STATUS_IS_ACTIVE( tr_torrentGetActivity( tor ) ) )
184        {
185            allPaused = 0;
186            break;
187        }
188    }
189   
190    /* if we have any running torrents, check for new incoming peer connections */
191    /* (jhujhiti):
192     * This has been changed from a loop that will end when the listener queue
193     * is exhausted to one that will only check for one connection at a time.
194     * I think it unlikely that we get many more than one connection in the
195     * time between pulses (currently one second). However, just to be safe,
196     * I have increased the length of the listener queue from 5 to 10
197     * (see acceptCb() above). */
198    if( !allPaused )
199        tr_socketListForEach( s->bindSockets, &acceptCb, s );
200}
201
202static int
203sharedPulse( void * vshared )
204{
205    tr_bool keepPulsing = 1;
206    tr_shared * shared = vshared;
207
208    natPulse( shared );
209
210    if( !shared->isShuttingDown )
211    {
212        incomingPeersPulse( shared );
213    }
214    else
215    {
216        tr_ninf( getKey( ), _( "Stopped" ) );
217        tr_timerFree( &shared->pulseTimer );
218        tr_socketListForEach( shared->bindSockets, &closeCb, shared );
219        tr_natpmpClose( shared->natpmp );
220        tr_upnpClose( shared->upnp );
221        shared->session->shared = NULL;
222        tr_free( shared );
223        keepPulsing = 0;
224    }
225
226    return keepPulsing;
227}
228
229/***
230****
231***/
232
233static tr_socketList *
234setupBindSockets( tr_port port )
235{
236    /* Do we care if an address is in use? Probably not, since it will be
237     * caught later. This will only set up the list of sockets to bind. */
238    int s4, s6;
239    tr_socketList * socks = NULL;
240    s6 = tr_netBindTCP( &tr_in6addr_any, port, TRUE );
241    if( s6 >= 0 || -s6 != EAFNOSUPPORT ) /* we support ipv6 */
242    {
243        socks = tr_socketListNew( &tr_in6addr_any );
244        listen( s6, 1 );
245    }
246    s4 = tr_netBindTCP( &tr_inaddr_any, port, TRUE );
247    if( s4 >= 0 ) /* we bound *with* the ipv6 socket bound (need both)
248                   * or only have ipv4 */
249    {
250        if( socks )
251            tr_socketListAppend( socks, &tr_inaddr_any );
252        else
253            socks = tr_socketListNew( &tr_inaddr_any );
254        tr_netClose( s4 );
255    }
256    if( s6 >= 0 )
257        tr_netClose( s6 );
258    return socks;
259}
260
261tr_shared *
262tr_sharedInit( tr_session  * session,
263               tr_bool       isEnabled,
264               tr_port       publicPort )
265{
266    tr_shared * s = tr_new0( tr_shared, 1 );
267
268    s->session      = session;
269    s->publicPort   = publicPort;
270    s->shouldChange = TRUE;
271    s->bindSockets  = setupBindSockets( publicPort );
272    s->shouldChange = TRUE;
273    s->natpmp       = tr_natpmpInit( );
274    s->upnp         = tr_upnpInit( );
275    s->pulseTimer   = tr_timerNew( session, sharedPulse, s, 1000 );
276    s->isEnabled    = isEnabled;
277    s->upnpStatus   = TR_PORT_UNMAPPED;
278    s->natpmpStatus = TR_PORT_UNMAPPED;
279
280    return s;
281}
282
283void
284tr_sharedShuttingDown( tr_shared * s )
285{
286    s->isShuttingDown = 1;
287}
288
289void
290tr_sharedSetPort( tr_shared * s, tr_port  port )
291{
292    tr_torrent * tor = NULL;
293
294    s->publicPort   = port;
295    s->shouldChange = TRUE;
296
297    while( ( tor = tr_torrentNext( s->session, tor ) ) )
298        tr_torrentChangeMyPort( tor );
299}
300
301tr_port
302tr_sharedGetPeerPort( const tr_shared * s )
303{
304    return s->publicPort;
305}
306
307void
308tr_sharedTraversalEnable( tr_shared * s, tr_bool isEnabled )
309{
310    s->isEnabled = isEnabled;
311}
312
313tr_bool
314tr_sharedTraversalIsEnabled( const tr_shared * s )
315{
316    return s->isEnabled;
317}
318
319int
320tr_sharedTraversalStatus( const tr_shared * s )
321{
322    return MAX( s->natpmpStatus, s->upnpStatus );
323}
324
Note: See TracBrowser for help on using the repository browser.