/ daemon / UPnP.cpp
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 */