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