1 /*
2  * Copyright (C) 2004-2020 by the Widelands Development Team
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  *
18  */
19 
20 #include "network/network_lan_promotion.h"
21 
22 #ifndef _WIN32
23 #include <ifaddrs.h>
24 #endif
25 
26 #include <memory>
27 
28 #include "base/i18n.h"
29 #include "base/log.h"
30 #include "base/warning.h"
31 #include "build_info.h"
32 #include "network/constants.h"
33 
34 namespace {
35 
36 /**
37  * Returns the IP version.
38  * \param addr The address object to get the IP version for.
39  * \return Either 4 or 6, depending on the version of the given address.
40  */
get_ip_version(const boost::asio::ip::address & addr)41 int get_ip_version(const boost::asio::ip::address& addr) {
42 	assert(!addr.is_unspecified());
43 	if (addr.is_v4()) {
44 		return 4;
45 	} else {
46 		assert(addr.is_v6());
47 		return 6;
48 	}
49 }
50 
51 /**
52  * Returns the IP version.
53  * \param version A whatever object to get the IP version for.
54  * \return Either 4 or 6, depending on the version of the given address.
55  */
get_ip_version(const boost::asio::ip::udp & version)56 int get_ip_version(const boost::asio::ip::udp& version) {
57 	if (version == boost::asio::ip::udp::v4()) {
58 		return 4;
59 	} else {
60 		assert(version == boost::asio::ip::udp::v6());
61 		return 6;
62 	}
63 }
64 }  // namespace
65 
66 /*** class LanBase ***/
67 /**
68  * \internal
69  * In an ideal world, we would use the same code with boost asio for all three operating systems.
70  * Unfortunately, it isn't that easy and we need some platform specific code.
71  * For IPv4, windows needs a special case: For Linux and Apple we have to iterate over all assigned
72  * IPv4
73  * addresses (e.g. 192.168.1.68), transform them to broadcast addresses (e.g. 192.168.1.255) and
74  * send our
75  * packets to those addresses. For windows, we simply can send to 255.255.255.255.
76  * For IPv6, Apple requires special handling. On the other two operating systems we can send to the
77  * multicast
78  * address ff02::1 (kind of a local broadcast) without specifying over which interface we want to
79  * send.
80  * On Apple we have to specify the interface, forcing us to send our message over all interfaces we
81  * can find.
82  */
LanBase(uint16_t port)83 LanBase::LanBase(uint16_t port) : io_service(), socket_v4(io_service), socket_v6(io_service) {
84 
85 #ifndef _WIN32
86 	// Iterate over all interfaces. If they support IPv4, store the broadcast-address
87 	// of the interface and try to start the socket. If they support IPv6, just start
88 	// the socket. There is one fixed broadcast-address for IPv6 (well, actually multicast)
89 
90 	// Adapted example out of "man getifaddrs"
91 	// TODO(Notabilis): I don't like this part. But boost is not able to iterate over
92 	// the local IPs and interfaces at this time. If they ever add it, replace this code
93 	struct ifaddrs *ifaddr, *ifa;
94 	int s, n;
95 	char host[NI_MAXHOST];
96 	if (getifaddrs(&ifaddr) == -1) {
97 		perror("getifaddrs");
98 		exit(EXIT_FAILURE);
99 	}
100 	for (ifa = ifaddr, n = 0; ifa != nullptr; ifa = ifa->ifa_next, n++) {
101 		if (ifa->ifa_addr == nullptr) {
102 			continue;
103 		}
104 		if (!(ifa->ifa_flags & IFF_LOOPBACK) && !(ifa->ifa_flags & IFF_BROADCAST) &&
105 		    !(ifa->ifa_flags & IFF_MULTICAST)) {
106 			continue;
107 		}
108 		switch (ifa->ifa_addr->sa_family) {
109 		case AF_INET:
110 			s = getnameinfo(ifa->ifa_broadaddr, sizeof(struct sockaddr_in), host, NI_MAXHOST, nullptr,
111 			                0, NI_NUMERICHOST);
112 			if (s == 0) {
113 				start_socket(&socket_v4, boost::asio::ip::udp::v4(), port);
114 				broadcast_addresses_v4.insert(host);
115 			}
116 			break;
117 		case AF_INET6:
118 #ifdef __APPLE__
119 			interface_indices_v6.insert(if_nametoindex(ifa->ifa_name));
120 #endif
121 			start_socket(&socket_v6, boost::asio::ip::udp::v6(), port);
122 			// No address to store here. There is only one "broadcast" address for IPv6
123 			break;
124 		}
125 	}
126 	freeifaddrs(ifaddr);
127 
128 #else
129 	//  As Microsoft does not seem to support if_nameindex, we just broadcast to
130 	//  INADDR_BROADCAST.
131 	broadcast_addresses_v4.insert("255.255.255.255");
132 #endif
133 
134 	if (!is_open()) {
135 		// Hm, not good. Just try to open them and hope for the best
136 		log("[LAN] Trying to open both sockets.\n");
137 		start_socket(&socket_v4, boost::asio::ip::udp::v4(), port);
138 		start_socket(&socket_v6, boost::asio::ip::udp::v6(), port);
139 	}
140 
141 	if (!is_open()) {
142 		// Still not open? Go back to main menu.
143 		log("[LAN] Error: No sockets could be opened.\n");
144 		report_network_error();
145 	}
146 
147 	for (const std::string& ip : broadcast_addresses_v4) {
148 		log("[LAN] Will broadcast to %s.\n", ip.c_str());
149 	}
150 	if (socket_v6.is_open()) {
151 		log("[LAN] Will broadcast for IPv6.\n");
152 	}
153 }
154 
~LanBase()155 LanBase::~LanBase() {
156 	close_socket(&socket_v4);
157 	close_socket(&socket_v6);
158 }
159 
is_available()160 bool LanBase::is_available() {
161 	const auto do_is_available = [this](boost::asio::ip::udp::socket& socket) -> bool {
162 		boost::system::error_code ec;
163 		bool available = (socket.is_open() && socket.available(ec) > 0);
164 		if (ec) {
165 			log("[LAN] Error when checking whether data is available on IPv%d socket, closing it: "
166 			    "%s.\n",
167 			    get_ip_version(socket.local_endpoint().protocol()), ec.message().c_str());
168 			close_socket(&socket);
169 			return false;
170 		}
171 		return available;
172 	};
173 
174 	return do_is_available(socket_v4) || do_is_available(socket_v6);
175 }
176 
is_open()177 bool LanBase::is_open() {
178 	return socket_v4.is_open() || socket_v6.is_open();
179 }
180 
receive(void * const buf,size_t const len,NetAddress * addr)181 ssize_t LanBase::receive(void* const buf, size_t const len, NetAddress* addr) {
182 	assert(buf != nullptr);
183 	assert(addr != nullptr);
184 	size_t recv_len = 0;
185 
186 	const auto do_receive = [this, &buf, &len, &recv_len,
187 	                         addr](boost::asio::ip::udp::socket& socket) -> bool {
188 		if (socket.is_open()) {
189 			try {
190 				if (socket.available() > 0) {
191 					boost::asio::ip::udp::endpoint sender_endpoint;
192 					recv_len = socket.receive_from(boost::asio::buffer(buf, len), sender_endpoint);
193 					*addr = NetAddress{sender_endpoint.address(), sender_endpoint.port()};
194 					assert(recv_len <= len);
195 					return true;
196 				}
197 			} catch (const boost::system::system_error& ec) {
198 				// Some network error. Close the socket
199 				log("[LAN] Error when receiving data on IPv%d socket, closing it: %s.\n",
200 				    get_ip_version(socket.local_endpoint().protocol()), ec.what());
201 				close_socket(&socket);
202 			}
203 		}
204 		// Nothing received
205 		return false;
206 	};
207 
208 	// Try to receive something somewhere
209 	if (!do_receive(socket_v4)) {
210 		do_receive(socket_v6);
211 	}
212 
213 	// Return how much has been received, might be 0
214 	return recv_len;
215 }
216 
send(void const * const buf,size_t const len,const NetAddress & addr)217 bool LanBase::send(void const* const buf, size_t const len, const NetAddress& addr) {
218 	boost::system::error_code ec;
219 	assert(addr.is_valid());
220 	// If this assert failed, then there is some bug in the code. NetAddress should only be filled
221 	// with valid IP addresses (e.g. no hostnames)
222 	assert(!ec);
223 	boost::asio::ip::udp::endpoint destination(addr.ip, addr.port);
224 	boost::asio::ip::udp::socket* socket = nullptr;
225 	if (destination.address().is_v4()) {
226 		socket = &socket_v4;
227 	} else if (destination.address().is_v6()) {
228 		socket = &socket_v6;
229 	} else {
230 		NEVER_HERE();
231 	}
232 	assert(socket != nullptr);
233 	if (!socket->is_open()) {
234 		// I think this shouldn't happen normally. It might happen, though, if we receive
235 		// a broadcast and learn the IP, then our sockets goes down, then we try to send
236 		log("[LAN] Error: trying to send to an IPv%d address but socket is not open.\n",
237 		    get_ip_version(addr.ip));
238 		return false;
239 	}
240 	socket->send_to(boost::asio::buffer(buf, len), destination, 0, ec);
241 	if (ec) {
242 		log("[LAN] Error when trying to send something over IPv%d, closing socket: %s.\n",
243 		    get_ip_version(addr.ip), ec.message().c_str());
244 		close_socket(socket);
245 		return false;
246 	}
247 	return true;
248 }
249 
broadcast(void const * const buf,size_t const len,uint16_t const port)250 bool LanBase::broadcast(void const* const buf, size_t const len, uint16_t const port) {
251 
252 	const auto do_broadcast = [this, buf, len, port](
253 	   boost::asio::ip::udp::socket& socket, const std::string& address) -> bool {
254 		if (socket.is_open()) {
255 			boost::system::error_code ec;
256 			boost::asio::ip::udp::endpoint destination(
257 			   boost::asio::ip::address::from_string(address), port);
258 			socket.send_to(boost::asio::buffer(buf, len), destination, 0, ec);
259 			if (!ec) {
260 				return true;
261 			}
262 #ifdef __APPLE__
263 			if (get_ip_version(destination.address()) == 4) {
264 #endif  // __APPLE__
265 				log("[LAN] Error when broadcasting on IPv%d socket to %s, closing it: %s.\n",
266 				    get_ip_version(destination.address()), address.c_str(), ec.message().c_str());
267 				close_socket(&socket);
268 #ifdef __APPLE__
269 			} else {
270 				log("[LAN] Error when broadcasting on IPv6 socket to %s: %s.\n", address.c_str(),
271 				    ec.message().c_str());
272 			}
273 #endif  // __APPLE__
274 		}
275 		return false;
276 	};
277 
278 	bool one_success = false;
279 
280 	// IPv4 broadcasting is the same for all
281 	for (const std::string& address : broadcast_addresses_v4) {
282 		one_success |= do_broadcast(socket_v4, address);
283 	}
284 #ifndef __APPLE__
285 	// For IPv6 on Linux and Windows just send on an undefined network interface
286 	one_success |= do_broadcast(socket_v6, "ff02::1");
287 #else   // __APPLE__
288 
289 	// Apple forces us to define which interface we want to send through
290 	for (auto it = interface_indices_v6.begin(); it != interface_indices_v6.end();) {
291 		socket_v6.set_option(boost::asio::ip::multicast::outbound_interface(*it));
292 		bool success = do_broadcast(socket_v6, "ff02::1");
293 		one_success |= success;
294 		if (!success) {
295 			// Remove this interface id from the set
296 			it = interface_indices_v6.erase(it);
297 			if (interface_indices_v6.empty()) {
298 				log("[LAN] Warning: No more multicast capable IPv6 interfaces. "
299 				    "Other LAN players won't find your game.\n");
300 			}
301 		} else {
302 			++it;
303 		}
304 	}
305 #endif  // __APPLE__
306 	return one_success;
307 }
308 
start_socket(boost::asio::ip::udp::socket * socket,boost::asio::ip::udp version,uint16_t port)309 void LanBase::start_socket(boost::asio::ip::udp::socket* socket,
310                            boost::asio::ip::udp version,
311                            uint16_t port) {
312 
313 	if (socket->is_open()) {
314 		return;
315 	}
316 
317 	boost::system::error_code ec;
318 	// Try to open the socket
319 	socket->open(version, ec);
320 	if (ec) {
321 		log("[LAN] Failed to start an IPv%d socket: %s.\n", get_ip_version(version),
322 		    ec.message().c_str());
323 		return;
324 	}
325 
326 	const boost::asio::socket_base::broadcast option_broadcast(true);
327 	socket->set_option(option_broadcast, ec);
328 	if (ec) {
329 		log("[LAN] Error setting options for IPv%d socket, closing socket: %s.\n",
330 		    get_ip_version(version), ec.message().c_str());
331 		// Retrieve the error code to avoid throwing but ignore it
332 		close_socket(socket);
333 		return;
334 	}
335 
336 	const boost::asio::socket_base::reuse_address option_reuse(true);
337 	socket->set_option(option_reuse, ec);
338 	// This one isn't really needed so ignore the error
339 
340 	if (version == boost::asio::ip::udp::v6()) {
341 		const boost::asio::ip::v6_only option_v6only(true);
342 		socket->set_option(option_v6only, ec);
343 		// This one might not be needed, ignore the error and see whether we fail on bind()
344 	}
345 
346 	socket->bind(boost::asio::ip::udp::endpoint(version, port), ec);
347 	if (ec) {
348 		log("[LAN] Error binding IPv%d socket to UDP port %d, closing socket: %s.\n",
349 		    get_ip_version(version), port, ec.message().c_str());
350 		close_socket(socket);
351 		return;
352 	}
353 
354 	log("[LAN] Started an IPv%d socket on UDP port %d.\n", get_ip_version(version), port);
355 }
356 
report_network_error()357 void LanBase::report_network_error() {
358 	// No socket open? Sorry, but we can't continue this way
359 	const std::vector<std::string> ports_list(
360 	   {boost::lexical_cast<std::string>(kWidelandsLanDiscoveryPort),
361 	    boost::lexical_cast<std::string>(kWidelandsLanPromotionPort),
362 	    boost::lexical_cast<std::string>(kWidelandsLanPort)});
363 
364 	throw WLWarning(_("Failed to use the local network!"),
365 	                /** TRANSLATORS: %s is a list of alternative ports with "or" */
366 	                _("Widelands was unable to use the local network. "
367 	                  "Maybe some other process is already running a server on port %s "
368 	                  "or your network setup is broken."),
369 	                i18n::localize_list(ports_list, i18n::ConcatenateWith::OR).c_str());
370 }
371 
close_socket(boost::asio::ip::udp::socket * socket)372 void LanBase::close_socket(boost::asio::ip::udp::socket* socket) {
373 	boost::system::error_code ec;
374 	if (socket->is_open()) {
375 		const boost::asio::ip::udp::endpoint& endpoint = socket->local_endpoint(ec);
376 		if (!ec) {
377 			log("[LAN] Closing an IPv%d socket.\n", get_ip_version(endpoint.protocol()));
378 		}
379 		socket->shutdown(boost::asio::ip::udp::socket::shutdown_both, ec);
380 		socket->close(ec);
381 	}
382 }
383 
384 /*** class LanGamePromoter ***/
385 
LanGamePromoter()386 LanGamePromoter::LanGamePromoter() : LanBase(kWidelandsLanPromotionPort) {
387 
388 	needupdate = true;
389 
390 	memset(&gameinfo, 0, sizeof(gameinfo));
391 	strncpy(gameinfo.magic, "GAME", sizeof(gameinfo.magic));
392 
393 	gameinfo.version = LAN_PROMOTION_PROTOCOL_VERSION;
394 	gameinfo.state = LAN_GAME_OPEN;
395 
396 	strncpy(gameinfo.gameversion, build_id().c_str(), sizeof(gameinfo.gameversion));
397 	gameinfo.gameversion[sizeof(gameinfo.gameversion) - 1] = '\0';
398 
399 	strncpy(gameinfo.hostname, boost::asio::ip::host_name().c_str(), sizeof(gameinfo.hostname));
400 	gameinfo.hostname[sizeof(gameinfo.hostname) - 1] = '\0';
401 }
402 
~LanGamePromoter()403 LanGamePromoter::~LanGamePromoter() {
404 	gameinfo.state = LAN_GAME_CLOSED;
405 
406 	// Don't care about errors at this point
407 	broadcast(&gameinfo, sizeof(gameinfo), kWidelandsLanDiscoveryPort);
408 }
409 
run()410 void LanGamePromoter::run() {
411 	if (needupdate) {
412 		needupdate = false;
413 
414 		if (!broadcast(&gameinfo, sizeof(gameinfo), kWidelandsLanDiscoveryPort)) {
415 			report_network_error();
416 		}
417 	}
418 
419 	while (is_available()) {
420 		char magic[8];
421 		NetAddress addr;
422 
423 		if (receive(magic, 8, &addr) < 8) {
424 			continue;
425 		}
426 
427 		log("Received %s packet from %s\n", magic, addr.ip.to_string().c_str());
428 
429 		if (!strncmp(magic, "QUERY", 6) && magic[6] == LAN_PROMOTION_PROTOCOL_VERSION) {
430 			if (!send(&gameinfo, sizeof(gameinfo), addr)) {
431 				report_network_error();
432 			}
433 		}
434 	}
435 }
436 
set_map(char const * map)437 void LanGamePromoter::set_map(char const* map) {
438 	strncpy(gameinfo.map, map, sizeof(gameinfo.map));
439 	gameinfo.map[sizeof(gameinfo.map) - 1] = '\0';
440 
441 	needupdate = true;
442 }
443 
444 /*** class LanGameFinder ***/
445 
LanGameFinder()446 LanGameFinder::LanGameFinder() : LanBase(kWidelandsLanDiscoveryPort), callback(nullptr) {
447 
448 	reset();
449 }
450 
reset()451 void LanGameFinder::reset() {
452 	char magic[8];
453 
454 	opengames.clear();
455 
456 	strncpy(magic, "QUERY", 8);
457 	magic[6] = LAN_PROMOTION_PROTOCOL_VERSION;
458 
459 	if (!broadcast(magic, 8, kWidelandsLanPromotionPort)) {
460 		report_network_error();
461 	}
462 }
463 
run()464 void LanGameFinder::run() {
465 	while (is_available()) {
466 		NetGameInfo info;
467 		NetAddress addr;
468 
469 		if (receive(&info, sizeof(info), &addr) < static_cast<int32_t>(sizeof(info))) {
470 			continue;
471 		}
472 
473 		log("Received %s packet from %s\n", info.magic, addr.ip.to_string().c_str());
474 
475 		if (strncmp(info.magic, "GAME", 6) || info.version != LAN_PROMOTION_PROTOCOL_VERSION) {
476 			continue;
477 		}
478 
479 		// Make sure that the callback function has been set before we do any callbacks
480 		if (!callback) {
481 			continue;
482 		}
483 
484 		//  if the game already is in the list, update the information
485 		//  otherwise just append it to the list
486 		bool was_in_list = false;
487 		for (const auto& opengame : opengames) {
488 			if (0 == strncmp(opengame->info.hostname, info.hostname, 128)) {
489 				opengame->info = info;
490 				if (!opengame->address.is_ipv6() && addr.is_ipv6()) {
491 					opengame->address.ip = addr.ip;
492 				}
493 				callback(GameUpdated, opengame.get(), userdata);
494 				was_in_list = true;
495 				break;
496 			}
497 		}
498 
499 		if (!was_in_list) {
500 			addr.port = kWidelandsLanPort;
501 			opengames.push_back(std::unique_ptr<NetOpenGame>(new NetOpenGame(addr, info)));
502 			callback(GameOpened, opengames.back().get(), userdata);
503 			break;
504 		}
505 	}
506 }
507 
set_callback(void (* const cb)(int32_t,const NetOpenGame * const,void *),void * const ud)508 void LanGameFinder::set_callback(void (*const cb)(int32_t, const NetOpenGame* const, void*),
509                                  void* const ud) {
510 	callback = cb;
511 	userdata = ud;
512 }
513