1 /*
2 Drawpile - a collaborative drawing program.
3
4 Copyright (C) 2013-2017 Calle Laakkonen
5
6 Drawpile is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Drawpile is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Drawpile. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "whatismyip.h"
21 #include "networkaccess.h"
22
23 #include <QDebug>
24 #include <QNetworkInterface>
25 #include <QNetworkReply>
26
27 #include <algorithm>
28
29 namespace {
30
isPublicAddress(const QHostAddress & address)31 bool isPublicAddress(const QHostAddress& address) {
32 // This could be a bit more comprehensive
33 if(address.protocol() == QAbstractSocket::IPv6Protocol) {
34 return address.scopeId() == "Global";
35
36 } else {
37 const quint32 a = address.toIPv4Address();
38 return !(
39 (a >> 24) == 10 ||
40 (a >> 24) == 127 ||
41 (a >> 16) == 0xC0A8
42 );
43 }
44 }
45
addressSort(const QHostAddress & a1,const QHostAddress & a2)46 bool addressSort(const QHostAddress& a1, const QHostAddress& a2)
47 {
48 // Sort an IP address list so public addresses are at the beginning of the list
49 uchar adr1[17], adr2[17];
50 adr1[0] = a1.isLoopback() ? 2 : 0 | !isPublicAddress(a1) ? 1 : 0;
51 adr2[0] = a2.isLoopback() ? 2 : 0 | !isPublicAddress(a2) ? 1 : 0;
52
53 Q_IPV6ADDR ip6;
54 ip6 = a1.toIPv6Address();
55 memcpy(adr1+1, &ip6, 16);
56
57 ip6 = a2.toIPv6Address();
58 memcpy(adr2+1, &ip6, 16);
59
60 return memcmp(adr1, adr2, 17) < 0 ;
61 }
62
63 }
64
WhatIsMyIp(QObject * parent)65 WhatIsMyIp::WhatIsMyIp(QObject *parent) :
66 QObject(parent), m_querying(false)
67 {
68 }
69
instance()70 WhatIsMyIp *WhatIsMyIp::instance()
71 {
72 static WhatIsMyIp *i;
73 if(!i)
74 i = new WhatIsMyIp();
75 return i;
76 }
77
discoverMyIp()78 void WhatIsMyIp::discoverMyIp()
79 {
80 if(m_querying)
81 return;
82 m_querying = true;
83
84 auto *filedownload = new networkaccess::FileDownload(this);
85 filedownload->setMaxSize(64);
86 filedownload->start(QUrl("https://ipecho.net/plain"));
87
88 connect(filedownload, &networkaccess::FileDownload::finished, this, [this, filedownload](const QString &errorMessage) {
89 filedownload->deleteLater();
90 if(!errorMessage.isEmpty()) {
91 qWarning("ipecho.net error: %s", qPrintable(errorMessage));
92 emit ipLookupError(errorMessage);
93 return;
94 }
95
96 QByteArray buf = filedownload->file()->readAll();
97 QHostAddress addr;
98 if(!addr.setAddress(QString::fromUtf8(buf))) {
99 qWarning() << "ipecho.net received invalid data:" << buf;
100 emit ipLookupError(tr("Received invalid data"));
101 } else {
102 emit myAddressIs(addr.toString());
103 m_querying = false;
104 }
105 });
106 }
107
isMyPrivateAddress(const QString & address)108 bool WhatIsMyIp::isMyPrivateAddress(const QString &address)
109 {
110 // Simple checks first
111 if(address == "localhost")
112 return true;
113
114 QHostAddress addr;
115 if(address.startsWith('[') && address.endsWith(']')) {
116 if(!addr.setAddress(address.mid(1, address.length()-2)))
117 return false;
118 } else {
119 if(!addr.setAddress(address))
120 return false;
121 }
122
123 if(addr.isLoopback())
124 return true;
125
126 if(isPublicAddress(addr))
127 return false;
128
129 // Check all host addresses
130 return QNetworkInterface::allAddresses().contains(addr);
131 }
132
isCGNAddress(const QString & address)133 bool WhatIsMyIp::isCGNAddress(const QString &address)
134 {
135 QHostAddress addr;
136
137 // remove port (if included). This breaks IPv6 addresses,
138 // but it doesn't matter since CGN is only used with IPv4.
139 int portsep = address.indexOf(':');
140 if(portsep>0)
141 addr = QHostAddress{address.left(portsep)};
142 else
143 addr = QHostAddress{address};
144
145 if(addr.isNull()) {
146 qWarning() << "unparseable address:" << address;
147 return false;
148 }
149
150 return addr.isInSubnet(QHostAddress(QStringLiteral("100.64.0.0")), 10);
151 }
152
153 /**
154 * Attempt to discover the address most likely reachable from the
155 * outside.
156 * @return server hostname
157 */
guessLocalAddress()158 QString WhatIsMyIp::guessLocalAddress()
159 {
160 QList<QNetworkInterface> list = QNetworkInterface::allInterfaces();
161 QList<QHostAddress> alist;
162
163 // Gather a list of acceptable addresses
164 for(const QNetworkInterface &iface : list) {
165 // Ignore inactive interfaces
166 if(!(iface.flags() & QNetworkInterface::IsUp) ||
167 !(iface.flags() & QNetworkInterface::IsRunning))
168 continue;
169
170 for(const QNetworkAddressEntry &entry : iface.addressEntries()) {
171 // Ignore IPv6 addresses with scope ID, because QUrl doesn't accept them (last tested with Qt 5.1.1)
172 QHostAddress a = entry.ip();
173 if(a.scopeId().isEmpty())
174 alist.append(a);
175 }
176 }
177
178 if (alist.count() > 0) {
179 std::sort(alist.begin(), alist.end(), addressSort);
180
181 QHostAddress a = alist.first();
182 if(a.protocol() == QAbstractSocket::IPv6Protocol)
183 return QString("[%1]").arg(a.toString());
184 return a.toString();
185 }
186
187 return "127.0.0.1";
188 }
189
190