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