1 // Aleth: Ethereum C++ client, tools and libraries.
2 // Copyright 2014-2019 Aleth Authors.
3 // Licensed under the GNU General Public License, Version 3.
4 
5 /// @file
6 /// UPnP port forwarding support.
7 #include "UPnP.h"
8 
9 #include <string.h>
10 #if ETH_MINIUPNPC
11 #include <miniupnpc/miniwget.h>
12 #include <miniupnpc/miniupnpc.h>
13 #include <miniupnpc/upnpcommands.h>
14 #endif
15 #include <libdevcore/Exceptions.h>
16 #include <libdevcore/CommonIO.h>
17 #include <libdevcore/Log.h>
18 using namespace std;
19 using namespace dev;
20 using namespace dev::p2p;
21 
UPnP()22 UPnP::UPnP()
23 {
24 #if ETH_MINIUPNPC
25 	m_urls = make_shared<UPNPUrls>();
26 	m_data = make_shared<IGDdatas>();
27 
28 	m_ok = false;
29 
30 	struct UPNPDev* devlist;
31 	struct UPNPDev* dev;
32 	char* descXML;
33 	int descXMLsize = 0;
34 	int upnperror = 0;
35 	memset(m_urls.get(), 0, sizeof(struct UPNPUrls));
36 	memset(m_data.get(), 0, sizeof(struct IGDdatas));
37 #if MINIUPNPC_API_VERSION >= 14
38 	devlist = upnpDiscover(2000, NULL/*multicast interface*/, NULL/*minissdpd socket path*/, 0/*sameport*/, 0/*ipv6*/, 2/*ttl*/, &upnperror);
39 #else
40 	devlist = upnpDiscover(2000, NULL/*multicast interface*/, NULL/*minissdpd socket path*/, 0/*sameport*/, 0/*ipv6*/, &upnperror);
41 #endif
42 	if (devlist)
43 	{
44 		dev = devlist;
45 		while (dev)
46 		{
47 			if (strstr (dev->st, "InternetGatewayDevice"))
48 				break;
49 			dev = dev->pNext;
50 		}
51 		if (!dev)
52 			dev = devlist; /* defaulting to first device */
53 
54 		cnote << "UPnP device:" << dev->descURL << "[st:" << dev->st << "]";
55 #if MINIUPNPC_API_VERSION >= 16
56 		int responsecode = 200;
57 		descXML = (char*)miniwget(dev->descURL, &descXMLsize, 0, &responsecode);
58 #elif MINIUPNPC_API_VERSION >= 9
59 		descXML = (char*)miniwget(dev->descURL, &descXMLsize, 0);
60 #else
61 		descXML = (char*)miniwget(dev->descURL, &descXMLsize);
62 #endif
63 		if (descXML)
64 		{
65 			parserootdesc(descXML, descXMLsize, m_data.get());
66 			free(descXML);
67 #if MINIUPNPC_API_VERSION >= 9
68 			GetUPNPUrls (m_urls.get(), m_data.get(), dev->descURL, 0);
69 #else
70 			GetUPNPUrls (m_urls.get(), m_data.get(), dev->descURL);
71 #endif
72 			m_ok = true;
73 		}
74 		freeUPNPDevlist(devlist);
75 	}
76 	else
77 #endif
78 	{
79 		cnote << "UPnP device not found.";
80 		BOOST_THROW_EXCEPTION(NoUPnPDevice());
81 	}
82 }
83 
~UPnP()84 UPnP::~UPnP()
85 {
86 	auto r = m_reg;
87 	for (auto i: r)
88 		removeRedirect(i);
89 }
90 
externalIP()91 string UPnP::externalIP()
92 {
93 #if ETH_MINIUPNPC
94 	char addr[16];
95 	if (!UPNP_GetExternalIPAddress(m_urls->controlURL, m_data->first.servicetype, addr))
96 		return addr;
97 #endif
98 	return "0.0.0.0";
99 }
100 
addRedirect(char const * _addr,int _port)101 int UPnP::addRedirect(char const* _addr, int _port)
102 {
103 	(void)_addr;
104 	(void)_port;
105 #if ETH_MINIUPNPC
106 	if (m_urls->controlURL[0] == '\0')
107 	{
108 		cwarn << "UPnP::addRedirect() called without proper initialisation?";
109 		return -1;
110 	}
111 
112 	// Try direct mapping first (port external, port internal).
113 	char port_str[16];
114 	char ext_port_str[16];
115 	sprintf(port_str, "%d", _port);
116 	if (!UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, port_str, _addr, "ethereum", "TCP", NULL, NULL))
117 		return _port;
118 
119 	// Failed - now try (random external, port internal) and cycle up to 10 times.
120 	srand(static_cast<unsigned int>(time(nullptr)));
121 	for (unsigned i = 0; i < 10; ++i)
122 	{
123 		_port = rand() % (32768 - 1024) + 1024;
124 		sprintf(ext_port_str, "%d", _port);
125 		if (!UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, ext_port_str, port_str, _addr, "ethereum", "TCP", NULL, NULL))
126 			return _port;
127 	}
128 
129 	// Failed. Try asking the router to give us a free external port.
130 	if (UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, NULL, _addr, "ethereum", "TCP", NULL, NULL))
131 		// Failed. Exit.
132 		return 0;
133 
134 	// We got mapped, but we don't know which ports we got mapped to. Now to find...
135 	unsigned num = 0;
136 	UPNP_GetPortMappingNumberOfEntries(m_urls->controlURL, m_data->first.servicetype, &num);
137 	for (unsigned i = 0; i < num; ++i)
138 	{
139 		char extPort[16];
140 		char intClient[16];
141 		char intPort[6];
142 		char protocol[4];
143 		char desc[80];
144 		char enabled[4];
145 		char rHost[64];
146 		char duration[16];
147 		UPNP_GetGenericPortMappingEntry(m_urls->controlURL, m_data->first.servicetype, toString(i).c_str(), extPort, intClient, intPort, protocol, desc, enabled, rHost, duration);
148 		if (string("ethereum") == desc)
149 		{
150 			m_reg.insert(atoi(extPort));
151 			return atoi(extPort);
152 		}
153 	}
154 	cerr << "ERROR: Mapped port not found." << endl;
155 #endif
156 	return 0;
157 }
158 
removeRedirect(int _port)159 void UPnP::removeRedirect(int _port)
160 {
161 	(void)_port;
162 #if ETH_MINIUPNPC
163 	char port_str[16];
164 	printf("TB : upnp_rem_redir (%d)\n", _port);
165 	if (m_urls->controlURL[0] == '\0')
166 	{
167 		printf("TB : the init was not done !\n");
168 		return;
169 	}
170 	sprintf(port_str, "%d", _port);
171 	UPNP_DeletePortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, "TCP", NULL);
172 	m_reg.erase(_port);
173 #endif
174 }
175