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: upnp.c 10910 2010-06-30 06:07:06Z charles $ |
---|
11 | */ |
---|
12 | |
---|
13 | #include <assert.h> |
---|
14 | #include <errno.h> |
---|
15 | |
---|
16 | #include <miniupnp/miniupnpc.h> |
---|
17 | #include <miniupnp/upnpcommands.h> |
---|
18 | |
---|
19 | #include "transmission.h" |
---|
20 | #include "port-forwarding.h" |
---|
21 | #include "session.h" |
---|
22 | #include "upnp.h" |
---|
23 | #include "utils.h" |
---|
24 | |
---|
25 | static const char * |
---|
26 | getKey( void ) { return _( "Port Forwarding (UPnP)" ); } |
---|
27 | |
---|
28 | typedef enum |
---|
29 | { |
---|
30 | TR_UPNP_IDLE, |
---|
31 | TR_UPNP_ERR, |
---|
32 | TR_UPNP_DISCOVER, |
---|
33 | TR_UPNP_MAP, |
---|
34 | TR_UPNP_UNMAP |
---|
35 | } |
---|
36 | tr_upnp_state; |
---|
37 | |
---|
38 | struct tr_upnp |
---|
39 | { |
---|
40 | tr_bool hasDiscovered; |
---|
41 | struct UPNPUrls urls; |
---|
42 | struct IGDdatas data; |
---|
43 | int port; |
---|
44 | char lanaddr[16]; |
---|
45 | unsigned int isMapped; |
---|
46 | tr_upnp_state state; |
---|
47 | }; |
---|
48 | |
---|
49 | /** |
---|
50 | *** |
---|
51 | **/ |
---|
52 | |
---|
53 | tr_upnp* |
---|
54 | tr_upnpInit( void ) |
---|
55 | { |
---|
56 | tr_upnp * ret = tr_new0( tr_upnp, 1 ); |
---|
57 | |
---|
58 | ret->state = TR_UPNP_DISCOVER; |
---|
59 | ret->port = -1; |
---|
60 | return ret; |
---|
61 | } |
---|
62 | |
---|
63 | void |
---|
64 | tr_upnpClose( tr_upnp * handle ) |
---|
65 | { |
---|
66 | assert( !handle->isMapped ); |
---|
67 | assert( ( handle->state == TR_UPNP_IDLE ) |
---|
68 | || ( handle->state == TR_UPNP_ERR ) |
---|
69 | || ( handle->state == TR_UPNP_DISCOVER ) ); |
---|
70 | |
---|
71 | if( handle->hasDiscovered ) |
---|
72 | FreeUPNPUrls( &handle->urls ); |
---|
73 | tr_free( handle ); |
---|
74 | } |
---|
75 | |
---|
76 | /** |
---|
77 | *** |
---|
78 | **/ |
---|
79 | |
---|
80 | enum |
---|
81 | { |
---|
82 | UPNP_IGD_NONE = 0, |
---|
83 | UPNP_IGD_VALID_CONNECTED = 1, |
---|
84 | UPNP_IGD_VALID_NOT_CONNECTED = 2, |
---|
85 | UPNP_IGD_INVALID = 3 |
---|
86 | }; |
---|
87 | |
---|
88 | int |
---|
89 | tr_upnpPulse( tr_upnp * handle, |
---|
90 | int port, |
---|
91 | int isEnabled, |
---|
92 | int doPortCheck ) |
---|
93 | { |
---|
94 | int ret; |
---|
95 | |
---|
96 | if( isEnabled && ( handle->state == TR_UPNP_DISCOVER ) ) |
---|
97 | { |
---|
98 | struct UPNPDev * devlist; |
---|
99 | errno = 0; |
---|
100 | devlist = upnpDiscover( 2000, NULL, NULL, 0 ); |
---|
101 | if( devlist == NULL ) |
---|
102 | { |
---|
103 | tr_ndbg( |
---|
104 | getKey( ), "upnpDiscover failed (errno %d - %s)", errno, |
---|
105 | tr_strerror( errno ) ); |
---|
106 | } |
---|
107 | errno = 0; |
---|
108 | if( UPNP_GetValidIGD( devlist, &handle->urls, &handle->data, |
---|
109 | handle->lanaddr, sizeof( handle->lanaddr ) ) == UPNP_IGD_VALID_CONNECTED ) |
---|
110 | { |
---|
111 | tr_ninf( getKey( ), _( |
---|
112 | "Found Internet Gateway Device \"%s\"" ), |
---|
113 | handle->urls.controlURL ); |
---|
114 | tr_ninf( getKey( ), _( |
---|
115 | "Local Address is \"%s\"" ), handle->lanaddr ); |
---|
116 | handle->state = TR_UPNP_IDLE; |
---|
117 | handle->hasDiscovered = 1; |
---|
118 | } |
---|
119 | else |
---|
120 | { |
---|
121 | handle->state = TR_UPNP_ERR; |
---|
122 | tr_ndbg( |
---|
123 | getKey( ), "UPNP_GetValidIGD failed (errno %d - %s)", |
---|
124 | errno, |
---|
125 | tr_strerror( errno ) ); |
---|
126 | tr_ndbg( |
---|
127 | getKey( ), |
---|
128 | "If your router supports UPnP, please make sure UPnP is enabled!" ); |
---|
129 | } |
---|
130 | freeUPNPDevlist( devlist ); |
---|
131 | } |
---|
132 | |
---|
133 | if( handle->state == TR_UPNP_IDLE ) |
---|
134 | { |
---|
135 | if( handle->isMapped && ( !isEnabled || ( handle->port != port ) ) ) |
---|
136 | handle->state = TR_UPNP_UNMAP; |
---|
137 | } |
---|
138 | |
---|
139 | if( isEnabled && handle->isMapped && doPortCheck ) |
---|
140 | { |
---|
141 | char portStr[8]; |
---|
142 | char intPort[8]; |
---|
143 | char intClient[16]; |
---|
144 | int i; |
---|
145 | |
---|
146 | tr_snprintf( portStr, sizeof( portStr ), "%d", handle->port ); |
---|
147 | i = UPNP_GetSpecificPortMappingEntry( handle->urls.controlURL, |
---|
148 | handle->data.first.servicetype, portStr, |
---|
149 | "TCP", intClient, intPort ); |
---|
150 | if( i != UPNPCOMMAND_SUCCESS ) |
---|
151 | { |
---|
152 | tr_ninf( getKey( ), _( "Port %d isn't forwarded" ), handle->port ); |
---|
153 | handle->isMapped = FALSE; |
---|
154 | } |
---|
155 | } |
---|
156 | |
---|
157 | if( handle->state == TR_UPNP_UNMAP ) |
---|
158 | { |
---|
159 | char portStr[16]; |
---|
160 | tr_snprintf( portStr, sizeof( portStr ), "%d", handle->port ); |
---|
161 | UPNP_DeletePortMapping( handle->urls.controlURL, |
---|
162 | handle->data.first.servicetype, |
---|
163 | portStr, "TCP", NULL ); |
---|
164 | tr_ninf( getKey( ), |
---|
165 | _( |
---|
166 | "Stopping port forwarding through \"%s\", service \"%s\"" ), |
---|
167 | handle->urls.controlURL, handle->data.first.servicetype ); |
---|
168 | handle->isMapped = 0; |
---|
169 | handle->state = TR_UPNP_IDLE; |
---|
170 | handle->port = -1; |
---|
171 | } |
---|
172 | |
---|
173 | if( handle->state == TR_UPNP_IDLE ) |
---|
174 | { |
---|
175 | if( isEnabled && !handle->isMapped ) |
---|
176 | handle->state = TR_UPNP_MAP; |
---|
177 | } |
---|
178 | |
---|
179 | if( handle->state == TR_UPNP_MAP ) |
---|
180 | { |
---|
181 | int err = -1; |
---|
182 | errno = 0; |
---|
183 | |
---|
184 | if( !handle->urls.controlURL || !handle->data.first.servicetype ) |
---|
185 | handle->isMapped = 0; |
---|
186 | else |
---|
187 | { |
---|
188 | char portStr[16]; |
---|
189 | char desc[64]; |
---|
190 | tr_snprintf( portStr, sizeof( portStr ), "%d", port ); |
---|
191 | tr_snprintf( desc, sizeof( desc ), "%s at %d", TR_NAME, port ); |
---|
192 | err = UPNP_AddPortMapping( handle->urls.controlURL, |
---|
193 | handle->data.first.servicetype, |
---|
194 | portStr, portStr, handle->lanaddr, |
---|
195 | desc, "TCP", NULL ); |
---|
196 | handle->isMapped = !err; |
---|
197 | } |
---|
198 | tr_ninf( getKey( ), |
---|
199 | _( |
---|
200 | "Port forwarding through \"%s\", service \"%s\". (local address: %s:%d)" ), |
---|
201 | handle->urls.controlURL, handle->data.first.servicetype, |
---|
202 | handle->lanaddr, port ); |
---|
203 | if( handle->isMapped ) |
---|
204 | { |
---|
205 | tr_ninf( getKey( ), "%s", _( "Port forwarding successful!" ) ); |
---|
206 | handle->port = port; |
---|
207 | handle->state = TR_UPNP_IDLE; |
---|
208 | } |
---|
209 | else |
---|
210 | { |
---|
211 | tr_ndbg( |
---|
212 | getKey( ), |
---|
213 | "Port forwarding failed with error %d (errno %d - %s)", err, |
---|
214 | errno, tr_strerror( errno ) ); |
---|
215 | tr_ndbg( |
---|
216 | getKey( ), |
---|
217 | "If your router supports UPnP, please make sure UPnP is enabled!" ); |
---|
218 | handle->port = -1; |
---|
219 | handle->state = TR_UPNP_ERR; |
---|
220 | } |
---|
221 | } |
---|
222 | |
---|
223 | switch( handle->state ) |
---|
224 | { |
---|
225 | case TR_UPNP_DISCOVER: |
---|
226 | ret = TR_PORT_UNMAPPED; break; |
---|
227 | |
---|
228 | case TR_UPNP_MAP: |
---|
229 | ret = TR_PORT_MAPPING; break; |
---|
230 | |
---|
231 | case TR_UPNP_UNMAP: |
---|
232 | ret = TR_PORT_UNMAPPING; break; |
---|
233 | |
---|
234 | case TR_UPNP_IDLE: |
---|
235 | ret = handle->isMapped ? TR_PORT_MAPPED |
---|
236 | : TR_PORT_UNMAPPED; break; |
---|
237 | |
---|
238 | default: |
---|
239 | ret = TR_PORT_ERROR; break; |
---|
240 | } |
---|
241 | |
---|
242 | return ret; |
---|
243 | } |
---|
244 | |
---|