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 */
7
8 /**
9 * @file tcp.cpp Basic functions to receive and send TCP packets.
10 */
11
12 #include "../../stdafx.h"
13 #include "../../debug.h"
14
15 #include "tcp.h"
16
17 #include "../../safeguards.h"
18
19 /**
20 * Construct a socket handler for a TCP connection.
21 * @param s The just opened TCP connection.
22 */
NetworkTCPSocketHandler(SOCKET s)23 NetworkTCPSocketHandler::NetworkTCPSocketHandler(SOCKET s) :
24 NetworkSocketHandler(),
25 packet_queue(nullptr), packet_recv(nullptr),
26 sock(s), writable(false)
27 {
28 }
29
~NetworkTCPSocketHandler()30 NetworkTCPSocketHandler::~NetworkTCPSocketHandler()
31 {
32 this->EmptyPacketQueue();
33 this->CloseSocket();
34 }
35
36 /**
37 * Free all pending and partially received packets.
38 */
EmptyPacketQueue()39 void NetworkTCPSocketHandler::EmptyPacketQueue()
40 {
41 while (this->packet_queue != nullptr) {
42 delete Packet::PopFromQueue(&this->packet_queue);
43 }
44 delete this->packet_recv;
45 this->packet_recv = nullptr;
46 }
47
48 /**
49 * Close the actual socket of the connection.
50 * Please make sure CloseConnection is called before CloseSocket, as
51 * otherwise not all resources might be released.
52 */
CloseSocket()53 void NetworkTCPSocketHandler::CloseSocket()
54 {
55 if (this->sock != INVALID_SOCKET) closesocket(this->sock);
56 this->sock = INVALID_SOCKET;
57 }
58
59 /**
60 * This will put this socket handler in a close state. It will not
61 * actually close the OS socket; use CloseSocket for this.
62 * @param error Whether we quit under an error condition or not.
63 * @return new status of the connection.
64 */
CloseConnection(bool error)65 NetworkRecvStatus NetworkTCPSocketHandler::CloseConnection(bool error)
66 {
67 this->MarkClosed();
68 this->writable = false;
69
70 this->EmptyPacketQueue();
71
72 return NETWORK_RECV_STATUS_OKAY;
73 }
74
75 /**
76 * This function puts the packet in the send-queue and it is send as
77 * soon as possible. This is the next tick, or maybe one tick later
78 * if the OS-network-buffer is full)
79 * @param packet the packet to send
80 */
SendPacket(Packet * packet)81 void NetworkTCPSocketHandler::SendPacket(Packet *packet)
82 {
83 assert(packet != nullptr);
84
85 packet->PrepareToSend();
86 Packet::AddToQueue(&this->packet_queue, packet);
87 }
88
89 /**
90 * Sends all the buffered packets out for this client. It stops when:
91 * 1) all packets are send (queue is empty)
92 * 2) the OS reports back that it can not send any more
93 * data right now (full network-buffer, it happens ;))
94 * 3) sending took too long
95 * @param closing_down Whether we are closing down the connection.
96 * @return \c true if a (part of a) packet could be sent and
97 * the connection is not closed yet.
98 */
SendPackets(bool closing_down)99 SendPacketsState NetworkTCPSocketHandler::SendPackets(bool closing_down)
100 {
101 ssize_t res;
102 Packet *p;
103
104 /* We can not write to this socket!! */
105 if (!this->writable) return SPS_NONE_SENT;
106 if (!this->IsConnected()) return SPS_CLOSED;
107
108 while ((p = this->packet_queue) != nullptr) {
109 res = p->TransferOut<int>(send, this->sock, 0);
110 if (res == -1) {
111 NetworkError err = NetworkError::GetLast();
112 if (!err.WouldBlock()) {
113 /* Something went wrong.. close client! */
114 if (!closing_down) {
115 Debug(net, 0, "Send failed: {}", err.AsString());
116 this->CloseConnection();
117 }
118 return SPS_CLOSED;
119 }
120 return SPS_PARTLY_SENT;
121 }
122 if (res == 0) {
123 /* Client/server has left us :( */
124 if (!closing_down) this->CloseConnection();
125 return SPS_CLOSED;
126 }
127
128 /* Is this packet sent? */
129 if (p->RemainingBytesToTransfer() == 0) {
130 /* Go to the next packet */
131 delete Packet::PopFromQueue(&this->packet_queue);
132 } else {
133 return SPS_PARTLY_SENT;
134 }
135 }
136
137 return SPS_ALL_SENT;
138 }
139
140 /**
141 * Receives a packet for the given client
142 * @return The received packet (or nullptr when it didn't receive one)
143 */
ReceivePacket()144 Packet *NetworkTCPSocketHandler::ReceivePacket()
145 {
146 ssize_t res;
147
148 if (!this->IsConnected()) return nullptr;
149
150 if (this->packet_recv == nullptr) {
151 this->packet_recv = new Packet(this, TCP_MTU);
152 }
153
154 Packet *p = this->packet_recv;
155
156 /* Read packet size */
157 if (!p->HasPacketSizeData()) {
158 while (p->RemainingBytesToTransfer() != 0) {
159 res = p->TransferIn<int>(recv, this->sock, 0);
160 if (res == -1) {
161 NetworkError err = NetworkError::GetLast();
162 if (!err.WouldBlock()) {
163 /* Something went wrong... */
164 if (!err.IsConnectionReset()) Debug(net, 0, "Recv failed: {}", err.AsString());
165 this->CloseConnection();
166 return nullptr;
167 }
168 /* Connection would block, so stop for now */
169 return nullptr;
170 }
171 if (res == 0) {
172 /* Client/server has left */
173 this->CloseConnection();
174 return nullptr;
175 }
176 }
177
178 /* Parse the size in the received packet and if not valid, close the connection. */
179 if (!p->ParsePacketSize()) {
180 this->CloseConnection();
181 return nullptr;
182 }
183 }
184
185 /* Read rest of packet */
186 while (p->RemainingBytesToTransfer() != 0) {
187 res = p->TransferIn<int>(recv, this->sock, 0);
188 if (res == -1) {
189 NetworkError err = NetworkError::GetLast();
190 if (!err.WouldBlock()) {
191 /* Something went wrong... */
192 if (!err.IsConnectionReset()) Debug(net, 0, "Recv failed: {}", err.AsString());
193 this->CloseConnection();
194 return nullptr;
195 }
196 /* Connection would block */
197 return nullptr;
198 }
199 if (res == 0) {
200 /* Client/server has left */
201 this->CloseConnection();
202 return nullptr;
203 }
204 }
205
206 /* Prepare for receiving a new packet */
207 this->packet_recv = nullptr;
208
209 p->PrepareToRead();
210 return p;
211 }
212
213 /**
214 * Check whether this socket can send or receive something.
215 * @return \c true when there is something to receive.
216 * @note Sets #writable if more data can be sent.
217 */
CanSendReceive()218 bool NetworkTCPSocketHandler::CanSendReceive()
219 {
220 fd_set read_fd, write_fd;
221 struct timeval tv;
222
223 FD_ZERO(&read_fd);
224 FD_ZERO(&write_fd);
225
226 FD_SET(this->sock, &read_fd);
227 FD_SET(this->sock, &write_fd);
228
229 tv.tv_sec = tv.tv_usec = 0; // don't block at all.
230 if (select(FD_SETSIZE, &read_fd, &write_fd, nullptr, &tv) < 0) return false;
231
232 this->writable = !!FD_ISSET(this->sock, &write_fd);
233 return FD_ISSET(this->sock, &read_fd) != 0;
234 }
235