Merge branch 'master' into jgrpp
[openttd-jgr.git] / src / network / core / udp.cpp
blobd76e01ce65cbb3cf8ce6ebf7e16b5e5f1177a2ae
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /**
9 * @file core/udp.cpp Basic functions to receive and send UDP packets.
12 #include "../../stdafx.h"
13 #include "../../date_func.h"
14 #include "../../debug.h"
15 #include "../../core/random_func.hpp"
16 #include "udp.h"
18 #include "../../safeguards.h"
20 /**
21 * Create an UDP socket but don't listen yet.
22 * @param bind the addresses to bind to.
24 NetworkUDPSocketHandler::NetworkUDPSocketHandler(NetworkAddressList *bind)
26 if (bind != nullptr) {
27 for (NetworkAddress &addr : *bind) {
28 this->bind.push_back(addr);
30 } else {
31 /* As an empty hostname and port 0 don't go well when
32 * resolving it we need to add an address for each of
33 * the address families we support. */
34 this->bind.emplace_back("", 0, AF_INET);
35 this->bind.emplace_back("", 0, AF_INET6);
38 this->fragment_token = ((uint64_t) InteractiveRandom()) | (((uint64_t) InteractiveRandom()) << 32);
42 /**
43 * Start listening on the given host and port.
44 * @return true if at least one port is listening
46 bool NetworkUDPSocketHandler::Listen()
48 /* Make sure socket is closed */
49 this->CloseSocket();
51 for (NetworkAddress &addr : this->bind) {
52 addr.Listen(SOCK_DGRAM, &this->sockets);
55 return !this->sockets.empty();
58 /**
59 * Close the actual UDP socket.
61 void NetworkUDPSocketHandler::CloseSocket()
63 for (auto &s : this->sockets) {
64 closesocket(s.first);
66 this->sockets.clear();
69 /**
70 * Send a packet over UDP
71 * @param p the packet to send
72 * @param recv the receiver (target) of the packet
73 * @param all send the packet using all sockets that can send it
74 * @param broadcast whether to send a broadcast message
76 void NetworkUDPSocketHandler::SendPacket(Packet &p, NetworkAddress &recv, bool all, bool broadcast, bool short_mtu)
78 if (this->sockets.empty()) this->Listen();
80 const uint MTU = short_mtu ? UDP_MTU_SHORT : UDP_MTU;
82 if (p.Size() > MTU) {
83 p.PrepareToSend();
85 uint64_t token = this->fragment_token++;
86 const uint PAYLOAD_MTU = MTU - (1 + 2 + 8 + 1 + 1 + 2);
88 const size_t packet_size = p.Size();
89 const uint8_t frag_count = (uint8_t)((packet_size + PAYLOAD_MTU - 1) / PAYLOAD_MTU);
91 Packet frag(this, PACKET_UDP_EX_MULTI);
92 uint8_t current_frag = 0;
93 size_t offset = 0;
94 while (offset < packet_size) {
95 uint16_t payload_size = (uint16_t)std::min<size_t>(PAYLOAD_MTU, packet_size - offset);
96 frag.Send_uint64(token);
97 frag.Send_uint8(current_frag);
98 frag.Send_uint8(frag_count);
99 frag.Send_uint16(payload_size);
100 frag.Send_binary(p.GetBufferData() + offset, payload_size);
101 current_frag++;
102 offset += payload_size;
103 this->SendPacket(frag, recv, all, broadcast, short_mtu);
104 frag.ResetState(PACKET_UDP_EX_MULTI);
106 assert_msg(current_frag == frag_count, "{}, {}", current_frag, frag_count);
107 return;
110 for (auto &s : this->sockets) {
111 /* Make a local copy because if we resolve it we cannot
112 * easily unresolve it so we can resolve it later again. */
113 NetworkAddress send(recv);
115 /* Not the same type */
116 if (!send.IsFamily(s.second.GetAddress()->ss_family)) continue;
118 p.PrepareToSend();
120 if (broadcast) {
121 /* Enable broadcast */
122 unsigned long val = 1;
123 if (setsockopt(s.first, SOL_SOCKET, SO_BROADCAST, (char *) &val, sizeof(val)) < 0) {
124 Debug(net, 1, "Setting broadcast mode failed: {}", NetworkError::GetLast().AsString());
128 /* Send the buffer */
129 ssize_t res = p.TransferOut<int>(sendto, s.first, 0, (const struct sockaddr *)send.GetAddress(), send.GetAddressLength());
130 Debug(net, 7, "sendto({})", FormatNetworkAddress(&send));
132 /* Check for any errors, but ignore it otherwise */
133 if (res == -1) Debug(net, 1, "sendto({}) failed with: {}", FormatNetworkAddress(&send), NetworkError::GetLast().AsString());
135 if (!all) break;
140 * Receive a packet at UDP level
142 void NetworkUDPSocketHandler::ReceivePackets()
144 for (auto &s : this->sockets) {
145 for (int i = 0; i < 1000; i++) { // Do not infinitely loop when DoSing with UDP
146 struct sockaddr_storage client_addr;
147 memset(&client_addr, 0, sizeof(client_addr));
149 /* The limit is UDP_MTU, but also allocate that much as we need to read the whole packet in one go. */
150 Packet p(Packet::ReadTag{}, this, UDP_MTU, UDP_MTU);
151 socklen_t client_len = sizeof(client_addr);
153 /* Try to receive anything */
154 SetNonBlocking(s.first); // Some OSes seem to lose the non-blocking status of the socket
155 ssize_t nbytes = p.TransferIn<int>(recvfrom, s.first, 0, (struct sockaddr *)&client_addr, &client_len);
157 /* Did we get the bytes for the base header of the packet? */
158 if (nbytes <= 0) break; // No data, i.e. no packet
159 if (nbytes <= 2) continue; // Invalid data; try next packet
160 #ifdef __EMSCRIPTEN__
161 client_len = FixAddrLenForEmscripten(client_addr);
162 #endif
164 NetworkAddress address(client_addr, client_len);
166 /* If the size does not match the packet must be corrupted.
167 * Otherwise it will be marked as corrupted later on. */
168 if (!p.ParsePacketSize() || static_cast<size_t>(nbytes) != p.Size()) {
169 Debug(net, 1, "received a packet with mismatching size from {}, ({}, {})", FormatNetworkAddress(&address), (uint)nbytes, (uint)p.Size());
170 continue;
172 if (!p.PrepareToRead()) {
173 Debug(net, 1, "Invalid packet received (too small / decryption error)");
174 continue;
177 /* Handle the packet */
178 this->HandleUDPPacket(p, address);
184 * Handle an incoming packets by sending it to the correct function.
185 * @param p the received packet
186 * @param client_addr the sender of the packet
188 void NetworkUDPSocketHandler::HandleUDPPacket(Packet &p, NetworkAddress &client_addr)
190 PacketUDPType type;
192 /* New packet == new client, which has not quit yet */
193 this->Reopen();
195 type = (PacketUDPType)p.Recv_uint8();
197 switch (this->HasClientQuit() ? PACKET_UDP_END : type) {
198 case PACKET_UDP_CLIENT_FIND_SERVER: this->Receive_CLIENT_FIND_SERVER(p, client_addr); break;
199 case PACKET_UDP_SERVER_RESPONSE: this->Receive_SERVER_RESPONSE(p, client_addr); break;
201 case PACKET_UDP_EX_MULTI: this->Receive_EX_MULTI(p, client_addr); break;
202 case PACKET_UDP_EX_SERVER_RESPONSE: this->Receive_EX_SERVER_RESPONSE(p, client_addr); break;
204 default:
205 if (this->HasClientQuit()) {
206 Debug(net, 0, "[udp] received invalid packet type {} from {}", type, FormatNetworkAddress(client_addr));
207 } else {
208 Debug(net, 0, "[udp] received illegal packet from {}", FormatNetworkAddress(client_addr));
210 break;
214 void NetworkUDPSocketHandler::Receive_EX_MULTI(Packet &p, NetworkAddress &client_addr)
216 uint64_t token = p.Recv_uint64();
217 uint8_t index = p.Recv_uint8 ();
218 uint8_t total = p.Recv_uint8 ();
219 uint16_t payload_size = p.Recv_uint16();
221 Debug(net, 6, "[udp] received multi-part packet from {}: {}, {}/{}, {} bytes",
222 FormatNetworkAddress(client_addr), token, index, total, payload_size);
224 if (total == 0 || index >= total) return;
225 if (!p.CanReadFromPacket(payload_size)) return;
227 time_t cur_time = time(nullptr);
229 auto add_to_fragment = [&](FragmentSet &fs) {
230 fs.fragments[index].assign((const char *) p.GetBufferData() + p.GetRawPos(), payload_size);
232 uint total_payload = 0;
233 for (auto &frag : fs.fragments) {
234 if (!frag.size()) return;
236 total_payload += (uint)frag.size();
239 Debug(net, 6, "[udp] merged multi-part packet from {}: {}, {} bytes",
240 FormatNetworkAddress(client_addr), token, total_payload);
242 Packet merged(Packet::ReadTag{}, this, TCP_MTU, 0);
243 merged.ReserveBuffer(total_payload);
244 for (auto &frag : fs.fragments) {
245 merged.Send_binary((const uint8_t *)frag.data(), frag.size());
247 merged.ParsePacketSize();
248 if (!merged.PrepareToRead()) return;
250 /* If the size does not match the packet must be corrupted.
251 * Otherwise it will be marked as corrupted later on. */
252 if (total_payload != merged.ReadRawPacketSize()) {
253 Debug(net, 1, "received an extended packet with mismatching size from {}, ({}, {})",
254 FormatNetworkAddress(client_addr), (uint)total_payload, (uint)merged.ReadRawPacketSize());
255 } else {
256 this->HandleUDPPacket(merged, client_addr);
259 fs = this->fragments.back();
260 this->fragments.pop_back();
263 uint i = 0;
264 while (i < this->fragments.size()) {
265 FragmentSet &fs = this->fragments[i];
266 if (fs.create_time < cur_time - 10) {
267 fs = this->fragments.back();
268 this->fragments.pop_back();
269 continue;
272 if (fs.token == token && fs.address == client_addr && fs.fragments.size() == total) {
273 add_to_fragment(fs);
274 return;
276 i++;
279 this->fragments.push_back({ token, client_addr, cur_time, {} });
280 this->fragments.back().fragments.resize(total);
281 add_to_fragment(this->fragments.back());
285 * Helper for logging receiving invalid packets.
286 * @param type The received packet type.
287 * @param client_addr The address we received the packet from.
289 void NetworkUDPSocketHandler::ReceiveInvalidPacket(PacketUDPType type, NetworkAddress &client_addr)
291 Debug(net, 0, "[udp] received packet type {} on wrong port from {}", type, FormatNetworkAddress(client_addr));
294 void NetworkUDPSocketHandler::Receive_CLIENT_FIND_SERVER(Packet &p, NetworkAddress &client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_CLIENT_FIND_SERVER, client_addr); }
295 void NetworkUDPSocketHandler::Receive_SERVER_RESPONSE(Packet &p, NetworkAddress &client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_RESPONSE, client_addr); }
296 void NetworkUDPSocketHandler::Receive_EX_SERVER_RESPONSE(Packet &p, NetworkAddress &client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_EX_SERVER_RESPONSE, client_addr); }