UPnP.cpp
1 /* 2 * Copyright (c) 2013-2024, The PurpleI2P Project 3 * 4 * This file is part of Purple i2pd project and licensed under BSD3 5 * 6 * See full license text in LICENSE file at top of project tree 7 */ 8 9 #ifdef USE_UPNP 10 #include <string> 11 #include <thread> 12 13 #include "Log.h" 14 15 #include "RouterContext.h" 16 #include "UPnP.h" 17 #include "NetDb.hpp" 18 #include "util.h" 19 #include "RouterInfo.h" 20 #include "Config.h" 21 22 #include <miniupnpc/miniupnpc.h> 23 #include <miniupnpc/upnpcommands.h> 24 25 namespace i2p 26 { 27 namespace transport 28 { 29 UPnP::UPnP () : m_IsRunning(false), m_Thread (nullptr), m_Timer (m_Service) 30 { 31 } 32 33 void UPnP::Stop () 34 { 35 if (m_IsRunning) 36 { 37 LogPrint(eLogInfo, "UPnP: Stopping"); 38 m_IsRunning = false; 39 m_Timer.cancel (); 40 m_Service.stop (); 41 if (m_Thread) 42 { 43 m_Thread->join (); 44 m_Thread.reset (nullptr); 45 } 46 CloseMapping (); 47 Close (); 48 } 49 } 50 51 void UPnP::Start() 52 { 53 m_IsRunning = true; 54 LogPrint(eLogInfo, "UPnP: Starting"); 55 boost::asio::post (m_Service, std::bind (&UPnP::Discover, this)); 56 std::unique_lock<std::mutex> l(m_StartedMutex); 57 m_Thread.reset (new std::thread (std::bind (&UPnP::Run, this))); 58 m_Started.wait_for (l, std::chrono::seconds (5)); // 5 seconds maximum 59 } 60 61 UPnP::~UPnP () 62 { 63 Stop (); 64 } 65 66 void UPnP::Run () 67 { 68 i2p::util::SetThreadName("UPnP"); 69 70 while (m_IsRunning) 71 { 72 try 73 { 74 m_Service.run (); 75 // Discover failed 76 break; // terminate the thread 77 } 78 catch (std::exception& ex) 79 { 80 LogPrint (eLogError, "UPnP: Runtime exception: ", ex.what ()); 81 PortMapping (); 82 } 83 } 84 } 85 86 void UPnP::Discover () 87 { 88 bool isError; 89 int err; 90 91 #if ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) 92 err = UPNPDISCOVER_SUCCESS; 93 94 #if (MINIUPNPC_API_VERSION >= 14) 95 m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0, 0, 2, &err); 96 #else 97 m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0, 0, &err); 98 #endif 99 100 isError = err != UPNPDISCOVER_SUCCESS; 101 #else // MINIUPNPC_API_VERSION >= 8 102 err = 0; 103 m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0); 104 isError = m_Devlist == NULL; 105 #endif // MINIUPNPC_API_VERSION >= 8 106 { 107 // notify starting thread 108 std::unique_lock<std::mutex> l(m_StartedMutex); 109 m_Started.notify_all (); 110 } 111 112 if (isError) 113 { 114 LogPrint (eLogError, "UPnP: Unable to discover Internet Gateway Devices: error ", err); 115 return; 116 } 117 118 #if (MINIUPNPC_API_VERSION >= 18) 119 err = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr), 120 m_externalIPAddress, sizeof (m_externalIPAddress)); 121 #else 122 err = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); 123 #endif 124 m_upnpUrlsInitialized=err!=0; 125 if (err == UPNP_IGD_VALID_CONNECTED) 126 { 127 #if (MINIUPNPC_API_VERSION < 18) 128 err = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); 129 if(err != UPNPCOMMAND_SUCCESS) 130 { 131 LogPrint (eLogError, "UPnP: Unable to get external address: error ", err); 132 return; 133 } 134 else 135 #endif 136 { 137 LogPrint (eLogError, "UPnP: Found Internet Gateway Device ", m_upnpUrls.controlURL); 138 if (!m_externalIPAddress[0]) 139 { 140 LogPrint (eLogError, "UPnP: Found Internet Gateway Device doesn't know our external address"); 141 return; 142 } 143 } 144 } 145 else 146 { 147 LogPrint (eLogError, "UPnP: Unable to find valid Internet Gateway Device: error ", err); 148 return; 149 } 150 151 // UPnP discovered 152 LogPrint (eLogDebug, "UPnP: ExternalIPAddress is ", m_externalIPAddress); 153 i2p::context.UpdateAddress (boost::asio::ip::make_address (m_externalIPAddress)); 154 // port mapping 155 PortMapping (); 156 } 157 158 int UPnP::CheckMapping (const char* port, const char* type) 159 { 160 int err = UPNPCOMMAND_SUCCESS; 161 162 #if (MINIUPNPC_API_VERSION >= 10) 163 err = UPNP_GetSpecificPortMappingEntry(m_upnpUrls.controlURL, m_upnpData.first.servicetype, port, type, NULL, NULL, NULL, NULL, NULL, NULL); 164 #elif ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) 165 err = UPNP_GetSpecificPortMappingEntry(m_upnpUrls.controlURL, m_upnpData.first.servicetype, port, type, NULL, NULL, NULL, NULL, NULL); 166 #else 167 err = UPNP_GetSpecificPortMappingEntry(m_upnpUrls.controlURL, m_upnpData.first.servicetype, port, type, NULL, NULL); 168 #endif 169 return err; 170 } 171 172 void UPnP::PortMapping () 173 { 174 auto a = context.GetRouterInfo().GetAddresses(); 175 if (!a) return; 176 for (const auto& address : *a) 177 { 178 if (address && !address->host.is_v6 () && address->port) 179 TryPortMapping (address); 180 } 181 m_Timer.expires_from_now (boost::posix_time::minutes(UPNP_PORT_FORWARDING_INTERVAL)); // every 20 minutes 182 m_Timer.async_wait ([this](const boost::system::error_code& ecode) 183 { 184 if (ecode != boost::asio::error::operation_aborted) 185 PortMapping (); 186 }); 187 } 188 189 void UPnP::TryPortMapping (std::shared_ptr<i2p::data::RouterInfo::Address> address) 190 { 191 std::string strType (GetProto (address)), strPort (std::to_string (address->port)); 192 std::string strDesc; i2p::config::GetOption("upnp.name", strDesc); 193 int err = UPNPCOMMAND_SUCCESS; 194 195 // check for existing mapping 196 err = CheckMapping (strPort.c_str (), strType.c_str ()); 197 if (err != UPNPCOMMAND_SUCCESS) // if mapping not found 198 { 199 LogPrint (eLogDebug, "UPnP: Port ", strPort, " is possibly not forwarded: return code ", err); 200 201 #if ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) 202 err = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), NULL, NULL); 203 #else 204 err = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), NULL); 205 #endif 206 if (err != UPNPCOMMAND_SUCCESS) 207 { 208 LogPrint (eLogError, "UPnP: Port forwarding to ", m_NetworkAddr, ":", strPort, " failed: return code ", err); 209 return; 210 } 211 else 212 { 213 LogPrint (eLogInfo, "UPnP: Port successfully forwarded (", m_externalIPAddress ,":", strPort, " type ", strType, " -> ", m_NetworkAddr ,":", strPort ,")"); 214 return; 215 } 216 } 217 else 218 { 219 LogPrint (eLogDebug, "UPnP: External forward from ", m_NetworkAddr, ":", strPort, " exists on current Internet Gateway Device"); 220 return; 221 } 222 } 223 224 void UPnP::CloseMapping () 225 { 226 auto a = context.GetRouterInfo().GetAddresses(); 227 if (!a) return; 228 for (const auto& address : *a) 229 { 230 if (address && !address->host.is_v6 () && address->port) 231 CloseMapping (address); 232 } 233 } 234 235 void UPnP::CloseMapping (std::shared_ptr<i2p::data::RouterInfo::Address> address) 236 { 237 if(!m_upnpUrlsInitialized) { 238 return; 239 } 240 std::string strType (GetProto (address)), strPort (std::to_string (address->port)); 241 int err = UPNPCOMMAND_SUCCESS; 242 243 err = CheckMapping (strPort.c_str (), strType.c_str ()); 244 if (err == UPNPCOMMAND_SUCCESS) 245 { 246 err = UPNP_DeletePortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), NULL); 247 LogPrint (eLogError, "UPnP: DeletePortMapping() returned : ", err); 248 } 249 } 250 251 void UPnP::Close () 252 { 253 freeUPNPDevlist (m_Devlist); 254 m_Devlist = 0; 255 if(m_upnpUrlsInitialized){ 256 FreeUPNPUrls (&m_upnpUrls); 257 m_upnpUrlsInitialized=false; 258 } 259 } 260 261 std::string UPnP::GetProto (std::shared_ptr<i2p::data::RouterInfo::Address> address) 262 { 263 switch (address->transportStyle) 264 { 265 case i2p::data::RouterInfo::eTransportNTCP2: 266 return "TCP"; 267 break; 268 case i2p::data::RouterInfo::eTransportSSU2: 269 default: 270 return "UDP"; 271 } 272 } 273 } 274 } 275 #else /* USE_UPNP */ 276 namespace i2p { 277 namespace transport { 278 } 279 } 280 #endif /* USE_UPNP */