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