1 | /* |
---|
2 | * This file Copyright (C) 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 12957 2011-10-09 02:05:52Z jordan $ |
---|
11 | */ |
---|
12 | |
---|
13 | #include <assert.h> |
---|
14 | #include <errno.h> |
---|
15 | |
---|
16 | #ifdef SYSTEM_MINIUPNP |
---|
17 | #include <miniupnpc/miniupnpc.h> |
---|
18 | #include <miniupnpc/upnpcommands.h> |
---|
19 | #else |
---|
20 | #include <miniupnp/miniupnpc.h> |
---|
21 | #include <miniupnp/upnpcommands.h> |
---|
22 | #endif |
---|
23 | |
---|
24 | #include "transmission.h" |
---|
25 | #include "port-forwarding.h" |
---|
26 | #include "session.h" |
---|
27 | #include "upnp.h" |
---|
28 | #include "utils.h" |
---|
29 | |
---|
30 | static const char * |
---|
31 | getKey( void ) { return _( "Port Forwarding (UPnP)" ); } |
---|
32 | |
---|
33 | typedef enum |
---|
34 | { |
---|
35 | TR_UPNP_IDLE, |
---|
36 | TR_UPNP_ERR, |
---|
37 | TR_UPNP_DISCOVER, |
---|
38 | TR_UPNP_MAP, |
---|
39 | TR_UPNP_UNMAP |
---|
40 | } |
---|
41 | tr_upnp_state; |
---|
42 | |
---|
43 | struct tr_upnp |
---|
44 | { |
---|
45 | bool hasDiscovered; |
---|
46 | struct UPNPUrls urls; |
---|
47 | struct IGDdatas data; |
---|
48 | int port; |
---|
49 | char lanaddr[16]; |
---|
50 | unsigned int isMapped; |
---|
51 | tr_upnp_state state; |
---|
52 | }; |
---|
53 | |
---|
54 | /** |
---|
55 | *** |
---|
56 | **/ |
---|
57 | |
---|
58 | tr_upnp* |
---|
59 | tr_upnpInit( void ) |
---|
60 | { |
---|
61 | tr_upnp * ret = tr_new0( tr_upnp, 1 ); |
---|
62 | |
---|
63 | ret->state = TR_UPNP_DISCOVER; |
---|
64 | ret->port = -1; |
---|
65 | return ret; |
---|
66 | } |
---|
67 | |
---|
68 | void |
---|
69 | tr_upnpClose( tr_upnp * handle ) |
---|
70 | { |
---|
71 | assert( !handle->isMapped ); |
---|
72 | assert( ( handle->state == TR_UPNP_IDLE ) |
---|
73 | || ( handle->state == TR_UPNP_ERR ) |
---|
74 | || ( handle->state == TR_UPNP_DISCOVER ) ); |
---|
75 | |
---|
76 | if( handle->hasDiscovered ) |
---|
77 | FreeUPNPUrls( &handle->urls ); |
---|
78 | tr_free( handle ); |
---|
79 | } |
---|
80 | |
---|
81 | /** |
---|
82 | *** Wrappers for miniupnpc functions |
---|
83 | **/ |
---|
84 | |
---|
85 | static struct UPNPDev * |
---|
86 | tr_upnpDiscover( int msec ) |
---|
87 | { |
---|
88 | int err = 0; |
---|
89 | struct UPNPDev * ret = NULL; |
---|
90 | |
---|
91 | #if defined(HAVE_MINIUPNP_16) |
---|
92 | ret = upnpDiscover( msec, NULL, NULL, 0, 0, &err ); |
---|
93 | #elif defined(HAVE_MINIUPNP_15) |
---|
94 | ret = upnpDiscover( msec, NULL, NULL, 0 ); |
---|
95 | #else |
---|
96 | ret = UPNPCOMMAND_UNKNOWN_ERROR; |
---|
97 | #endif |
---|
98 | |
---|
99 | if( ret != UPNPCOMMAND_SUCCESS ) |
---|
100 | tr_ndbg( getKey( ), "upnpDiscover failed (errno %d - %s)", err, tr_strerror( err ) ); |
---|
101 | |
---|
102 | return ret; |
---|
103 | } |
---|
104 | |
---|
105 | static int |
---|
106 | tr_upnpGetSpecificPortMappingEntry( tr_upnp * handle, const char * proto ) |
---|
107 | { |
---|
108 | int err; |
---|
109 | char intClient[16]; |
---|
110 | char intPort[16]; |
---|
111 | char portStr[16]; |
---|
112 | |
---|
113 | *intClient = '\0'; |
---|
114 | *intPort = '\0'; |
---|
115 | |
---|
116 | tr_snprintf( portStr, sizeof( portStr ), "%d", (int)handle->port ); |
---|
117 | |
---|
118 | #if defined(HAVE_MINIUPNP_16) |
---|
119 | err = UPNP_GetSpecificPortMappingEntry( handle->urls.controlURL, handle->data.first.servicetype, portStr, proto, intClient, intPort, NULL, NULL, NULL ); |
---|
120 | #elif defined(HAVE_MINIUPNP_15) |
---|
121 | err = UPNP_GetSpecificPortMappingEntry( handle->urls.controlURL, handle->data.first.servicetype, portStr, proto, intClient, intPort ); |
---|
122 | #else |
---|
123 | err = UPNPCOMMAND_UNKNOWN_ERROR; |
---|
124 | #endif |
---|
125 | |
---|
126 | return err; |
---|
127 | } |
---|
128 | |
---|
129 | static int |
---|
130 | tr_upnpAddPortMapping( const tr_upnp * handle, const char * proto, tr_port port, const char * desc ) |
---|
131 | { |
---|
132 | int err; |
---|
133 | const int old_errno = errno; |
---|
134 | char portStr[16]; |
---|
135 | errno = 0; |
---|
136 | |
---|
137 | tr_snprintf( portStr, sizeof( portStr ), "%d", (int)port ); |
---|
138 | |
---|
139 | #if defined(HAVE_MINIUPNP_16) |
---|
140 | err = UPNP_AddPortMapping( handle->urls.controlURL, handle->data.first.servicetype, portStr, portStr, handle->lanaddr, desc, proto, NULL, NULL ); |
---|
141 | #elif defined(HAVE_MINIUPNP_15) |
---|
142 | err = UPNP_AddPortMapping( handle->urls.controlURL, handle->data.first.servicetype, portStr, portStr, handle->lanaddr, desc, proto, NULL ); |
---|
143 | #else |
---|
144 | err = UPNPCOMMAND_UNKNOWN_ERROR; |
---|
145 | #endif |
---|
146 | |
---|
147 | if( err ) |
---|
148 | tr_ndbg( getKey( ), "%s Port forwarding failed with error %d (errno %d - %s)", proto, err, errno, tr_strerror( errno ) ); |
---|
149 | |
---|
150 | errno = old_errno; |
---|
151 | return err; |
---|
152 | } |
---|
153 | |
---|
154 | static void |
---|
155 | tr_upnpDeletePortMapping( const tr_upnp * handle, const char * proto, tr_port port ) |
---|
156 | { |
---|
157 | char portStr[16]; |
---|
158 | |
---|
159 | tr_snprintf( portStr, sizeof( portStr ), "%d", (int)port ); |
---|
160 | |
---|
161 | UPNP_DeletePortMapping( handle->urls.controlURL, |
---|
162 | handle->data.first.servicetype, |
---|
163 | portStr, proto, NULL ); |
---|
164 | } |
---|
165 | |
---|
166 | /** |
---|
167 | *** |
---|
168 | **/ |
---|
169 | |
---|
170 | enum |
---|
171 | { |
---|
172 | UPNP_IGD_NONE = 0, |
---|
173 | UPNP_IGD_VALID_CONNECTED = 1, |
---|
174 | UPNP_IGD_VALID_NOT_CONNECTED = 2, |
---|
175 | UPNP_IGD_INVALID = 3 |
---|
176 | }; |
---|
177 | |
---|
178 | int |
---|
179 | tr_upnpPulse( tr_upnp * handle, |
---|
180 | int port, |
---|
181 | int isEnabled, |
---|
182 | int doPortCheck ) |
---|
183 | { |
---|
184 | int ret; |
---|
185 | |
---|
186 | if( isEnabled && ( handle->state == TR_UPNP_DISCOVER ) ) |
---|
187 | { |
---|
188 | struct UPNPDev * devlist; |
---|
189 | |
---|
190 | devlist = tr_upnpDiscover( 2000 ); |
---|
191 | |
---|
192 | errno = 0; |
---|
193 | if( UPNP_GetValidIGD( devlist, &handle->urls, &handle->data, |
---|
194 | handle->lanaddr, sizeof( handle->lanaddr ) ) == UPNP_IGD_VALID_CONNECTED ) |
---|
195 | { |
---|
196 | tr_ninf( getKey( ), _( |
---|
197 | "Found Internet Gateway Device \"%s\"" ), |
---|
198 | handle->urls.controlURL ); |
---|
199 | tr_ninf( getKey( ), _( |
---|
200 | "Local Address is \"%s\"" ), handle->lanaddr ); |
---|
201 | handle->state = TR_UPNP_IDLE; |
---|
202 | handle->hasDiscovered = 1; |
---|
203 | } |
---|
204 | else |
---|
205 | { |
---|
206 | handle->state = TR_UPNP_ERR; |
---|
207 | tr_ndbg( |
---|
208 | getKey( ), "UPNP_GetValidIGD failed (errno %d - %s)", |
---|
209 | errno, |
---|
210 | tr_strerror( errno ) ); |
---|
211 | tr_ndbg( |
---|
212 | getKey( ), |
---|
213 | "If your router supports UPnP, please make sure UPnP is enabled!" ); |
---|
214 | } |
---|
215 | freeUPNPDevlist( devlist ); |
---|
216 | } |
---|
217 | |
---|
218 | if( handle->state == TR_UPNP_IDLE ) |
---|
219 | { |
---|
220 | if( handle->isMapped && ( !isEnabled || ( handle->port != port ) ) ) |
---|
221 | handle->state = TR_UPNP_UNMAP; |
---|
222 | } |
---|
223 | |
---|
224 | if( isEnabled && handle->isMapped && doPortCheck ) |
---|
225 | { |
---|
226 | if( ( tr_upnpGetSpecificPortMappingEntry( handle, "TCP" ) != UPNPCOMMAND_SUCCESS ) || |
---|
227 | ( tr_upnpGetSpecificPortMappingEntry( handle, "UDP" ) != UPNPCOMMAND_SUCCESS ) ) |
---|
228 | { |
---|
229 | tr_ninf( getKey( ), _( "Port %d isn't forwarded" ), handle->port ); |
---|
230 | handle->isMapped = false; |
---|
231 | } |
---|
232 | } |
---|
233 | |
---|
234 | if( handle->state == TR_UPNP_UNMAP ) |
---|
235 | { |
---|
236 | tr_upnpDeletePortMapping( handle, "TCP", handle->port ); |
---|
237 | tr_upnpDeletePortMapping( handle, "UDP", handle->port ); |
---|
238 | |
---|
239 | tr_ninf( getKey( ), |
---|
240 | _( "Stopping port forwarding through \"%s\", service \"%s\"" ), |
---|
241 | handle->urls.controlURL, handle->data.first.servicetype ); |
---|
242 | |
---|
243 | handle->isMapped = 0; |
---|
244 | handle->state = TR_UPNP_IDLE; |
---|
245 | handle->port = -1; |
---|
246 | } |
---|
247 | |
---|
248 | if( handle->state == TR_UPNP_IDLE ) |
---|
249 | { |
---|
250 | if( isEnabled && !handle->isMapped ) |
---|
251 | handle->state = TR_UPNP_MAP; |
---|
252 | } |
---|
253 | |
---|
254 | if( handle->state == TR_UPNP_MAP ) |
---|
255 | { |
---|
256 | int err_tcp = -1; |
---|
257 | int err_udp = -1; |
---|
258 | errno = 0; |
---|
259 | |
---|
260 | if( !handle->urls.controlURL || !handle->data.first.servicetype ) |
---|
261 | handle->isMapped = 0; |
---|
262 | else |
---|
263 | { |
---|
264 | char desc[64]; |
---|
265 | tr_snprintf( desc, sizeof( desc ), "%s at %d", TR_NAME, port ); |
---|
266 | |
---|
267 | err_tcp = tr_upnpAddPortMapping( handle, "TCP", port, desc ); |
---|
268 | err_udp = tr_upnpAddPortMapping( handle, "UDP", port, desc ); |
---|
269 | |
---|
270 | handle->isMapped = !err_tcp | !err_udp; |
---|
271 | } |
---|
272 | tr_ninf( getKey( ), |
---|
273 | _( "Port forwarding through \"%s\", service \"%s\". (local address: %s:%d)" ), |
---|
274 | handle->urls.controlURL, handle->data.first.servicetype, |
---|
275 | handle->lanaddr, port ); |
---|
276 | if( handle->isMapped ) |
---|
277 | { |
---|
278 | tr_ninf( getKey( ), "%s", _( "Port forwarding successful!" ) ); |
---|
279 | handle->port = port; |
---|
280 | handle->state = TR_UPNP_IDLE; |
---|
281 | } |
---|
282 | else |
---|
283 | { |
---|
284 | tr_ndbg( getKey( ), "If your router supports UPnP, please make sure UPnP is enabled!" ); |
---|
285 | handle->port = -1; |
---|
286 | handle->state = TR_UPNP_ERR; |
---|
287 | } |
---|
288 | } |
---|
289 | |
---|
290 | switch( handle->state ) |
---|
291 | { |
---|
292 | case TR_UPNP_DISCOVER: |
---|
293 | ret = TR_PORT_UNMAPPED; break; |
---|
294 | |
---|
295 | case TR_UPNP_MAP: |
---|
296 | ret = TR_PORT_MAPPING; break; |
---|
297 | |
---|
298 | case TR_UPNP_UNMAP: |
---|
299 | ret = TR_PORT_UNMAPPING; break; |
---|
300 | |
---|
301 | case TR_UPNP_IDLE: |
---|
302 | ret = handle->isMapped ? TR_PORT_MAPPED |
---|
303 | : TR_PORT_UNMAPPED; break; |
---|
304 | |
---|
305 | default: |
---|
306 | ret = TR_PORT_ERROR; break; |
---|
307 | } |
---|
308 | |
---|
309 | return ret; |
---|
310 | } |
---|
311 | |
---|