1 /*
2 * gtkatlantic - the gtk+ monopd client, enjoy network monopoly games
3 *
4 *
5 * Copyright © 2002-2015 Sylvain Rochet
6 *
7 * gtkatlantic is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; see the file COPYING. If not, see
19 * <http://www.gnu.org/licenses/>.
20 */
21
22 /* ** functions for connect/disconnect server, receive/send data to/from server */
23
24 #include "config.h"
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <errno.h>
31
32 #ifndef WIN32
33
34 #include <netdb.h>
35 #include <sys/socket.h>
36 #include <arpa/inet.h>
37 #include <netinet/in.h>
38 #include <netinet/tcp.h>
39 #include <fcntl.h>
40 #define closesocket close
41 #define my_io_channel_socket_new g_io_channel_unix_new
42
43 #else /* WIN32 */
44
45 #undef _WIN32_WINNT
46 #define _WIN32_WINNT _WIN32_WINNT_WINXP /* getaddrinfo() is only supported since WinXP */
47 #include <winsock2.h>
48 #include <ws2tcpip.h>
49 #include <mstcpip.h>
50 #define my_io_channel_socket_new g_io_channel_win32_new_socket
51 #ifndef SHUT_RDWR
52 #define SHUT_RDWR SD_BOTH
53 #endif /* SHUT_RDWR */
54
55 #endif /* WIN32 */
56
57 #include <sys/time.h>
58 #include <sys/types.h>
59 #include <time.h>
60
61 #include "xmlparse.h"
62 #include "global.h"
63 #include "client.h"
64 #include "game.h"
65 #include "interface.h"
66
67 static void* client_connect_async(connection *c);
68 static gboolean client_connect_finish(connection *c);
69 static gboolean client_connect_failed(gchar *msg);
70 static void client_connect_free(connection *c);
71
72 /* -- connect to server
73 >
74 > return false if:
75 > - max number of connection reached
76 > - can't resolve host
77 > - can't open port or socket
78 */
79 #if DEBUG
80 static guint32 nextid;
81 #endif /* DEBUG */
client_connect(guint8 type,gchar * host,gint32 port,gchar * cmd)82 void client_connect(guint8 type, gchar* host, gint32 port, gchar *cmd) {
83 connection *c;
84 GThread *t;
85 gchar *msg;
86
87 switch (type) {
88 case CONNECT_TYPE_MONOPD_GETGAME:
89 client_disconnect(global->customserver_connect);
90 break;
91 case CONNECT_TYPE_MONOPD_GAME:
92 client_disconnect(global->game_connect);
93 break;
94 case CONNECT_TYPE_METASERVER:
95 client_disconnect(global->metaserver_connect);
96 break;
97 }
98
99 c = g_malloc0(sizeof(connection));
100 #if DEBUG
101 c->id = nextid++;
102 #endif /* DEBUG */
103 c->type = type;
104 c->host = g_strdup(host);
105 c->port = port;
106 c->buffer_out = cmd;
107
108 msg = g_strdup_printf("Connecting to %s:%d…", c->host, c->port);
109 interface_set_infolabel(msg, "008000", true);
110 g_free(msg);
111
112 t = g_thread_new("connect", (GThreadFunc)client_connect_async, c);
113 g_thread_unref(t);
114 }
115
client_connect_async(connection * c)116 static void* client_connect_async(connection *c) {
117 struct addrinfo hints;
118 struct addrinfo *result, *addrinfo;
119 char port_str[6];
120 int r;
121 char ip_str[INET6_ADDRSTRLEN];
122 int sock;
123
124 memset(&hints, 0, sizeof(struct addrinfo));
125 hints.ai_family = AF_UNSPEC;
126 hints.ai_socktype = SOCK_STREAM;
127 snprintf(port_str, sizeof(port_str), "%d", c->port);
128
129 r = getaddrinfo(c->host, port_str, &hints, &result);
130 if (r != 0) {
131 gchar *msg = g_strdup_printf("Can't resolve host %s:%d: %s", c->host, c->port, gai_strerror(r));
132 g_idle_add((GSourceFunc)client_connect_failed, msg);
133 goto failed;
134 }
135
136 for (addrinfo = result; addrinfo != NULL; addrinfo = addrinfo->ai_next) {
137 sock = socket(addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol);
138 if (sock < 0) {
139 gchar *msg = g_strdup_printf("Socket failed %s:%d: %s", c->host, c->port, strerror(errno));
140 g_idle_add((GSourceFunc)client_connect_failed, msg);
141 continue;
142 }
143
144 ip_str[0] = '\0';
145 #ifndef WIN32
146 if (addrinfo->ai_family == AF_INET) {
147 inet_ntop(addrinfo->ai_family, &(((struct sockaddr_in *)addrinfo->ai_addr)->sin_addr), ip_str, INET6_ADDRSTRLEN);
148 } else if (addrinfo->ai_family == AF_INET6) {
149 inet_ntop(addrinfo->ai_family, &(((struct sockaddr_in6 *)addrinfo->ai_addr)->sin6_addr), ip_str, INET6_ADDRSTRLEN);
150 }
151 #else /* WIN32 */
152 if (addrinfo->ai_family == AF_INET) {
153 DWORD len = INET6_ADDRSTRLEN;
154 WSAAddressToString(addrinfo->ai_addr, (DWORD)sizeof(struct sockaddr_in), NULL, ip_str, &len);
155 } else if (addrinfo->ai_family == AF_INET6) {
156 DWORD len = INET6_ADDRSTRLEN;
157 WSAAddressToString(addrinfo->ai_addr, (DWORD)sizeof(struct sockaddr_in6), NULL, ip_str, &len);
158 }
159 #endif /* WIN32 */
160
161 r = connect(sock, addrinfo->ai_addr, addrinfo->ai_addrlen);
162 if (r < 0) {
163 gchar *msg = g_strdup_printf("Connect failed to %s:%d, %s: %s", c->host, c->port, ip_str, strerror(errno));
164 g_idle_add((GSourceFunc)client_connect_failed, msg);
165 close(sock);
166 continue;
167 }
168 break;
169 }
170
171 freeaddrinfo(result);
172
173 if (addrinfo == NULL) {
174 goto failed;
175 }
176
177 #ifndef WIN32
178 /* set socket to non-blocking */
179 int flags = fcntl(sock, F_GETFL);
180 if (flags >= 0) {
181 flags |= O_NONBLOCK;
182 fcntl(sock, F_SETFL, flags);
183 }
184 #else /* WIN32 */
185 u_long on = 1;
186 ioctlsocket(sock, FIONBIO, &on);
187 #endif /* WIN32 */
188
189 /* Disable Nagle's algorithm */
190 int opt = 1;
191 setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&opt, sizeof(opt));
192
193 /*
194 * Player can stay for a while in the get game window or game lobby without receiving or sending
195 * any packet. Some end user broken network equipment forget about idle TCP sessions more sooner
196 * than acceptable. Enable TCP keepalive to prevent that.
197 */
198 #ifdef WIN32 /* keepalive */
199 DWORD bytesret = 0;
200 struct tcp_keepalive keepalive;
201 keepalive.onoff = TRUE;
202 keepalive.keepalivetime = 10000; /* ms */
203 keepalive.keepaliveinterval = 10000; /* ms */
204
205 if (WSAIoctl(sock, SIO_KEEPALIVE_VALS, &keepalive, sizeof(keepalive), NULL, 0, &bytesret, NULL, NULL)) {
206 fprintf(stderr, "WSAIotcl(SIO_KEEPALIVE_VALS) failed on socket %d: %d\n", sock, WSAGetLastError());
207 }
208 #elif defined(SO_KEEPALIVE)
209 opt = 1;
210 if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&opt, sizeof(opt))) {
211 fprintf(stderr, "setsockopt() SO_KEEPALIVE failed on socket %d: %s\n", sock, strerror(errno));
212 }
213 #if defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL) && defined(TCP_KEEPCNT)
214 /* 10s idle before keepalive probe */
215 opt = 10;
216 if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, (char*)&opt, sizeof(opt))) {
217 fprintf(stderr, "setsockopt() TCP_KEEPIDLE failed on socket %d: %s\n", sock, strerror(errno));
218 }
219 /* 10s probe interval */
220 opt = 10;
221 if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, (char*)&opt, sizeof(opt))) {
222 fprintf(stderr, "setsockopt() TCP_KEEPINTVL failed on socket %d: %s\n", sock, strerror(errno));
223 }
224 /* consider session down after 10 missed probes */
225 opt = 10;
226 if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (char*)&opt, sizeof(opt))) {
227 fprintf(stderr, "setsockopt() TCP_KEEPCNT failed on socket %d: %s\n", sock, strerror(errno));
228 }
229 #endif /* TCP_KEEPIDLE && TCP_KEEPINTVL && TCP_KEEPCNT */
230 #endif /* keepalive */
231
232 c->ip = g_strdup(ip_str);
233 c->socket = sock;
234 g_idle_add((GSourceFunc)client_connect_finish, c);
235 return NULL;
236
237 failed:
238 g_free(c->host);
239 if (c->buffer_out) {
240 g_free(c->buffer_out);
241 }
242 g_free(c);
243 return NULL;
244 }
245
client_connect_finish(connection * c)246 static gboolean client_connect_finish(connection *c) {
247 GIOChannel *channel;
248 gchar *msg;
249
250 /* we are a bit too late */
251 if ( (c->type == CONNECT_TYPE_MONOPD_GETGAME && global->customserver_connect)
252 || (c->type == CONNECT_TYPE_MONOPD_GETGAME && global->phase != PHASE_GETGAMES)
253 || (c->type == CONNECT_TYPE_METASERVER && global->metaserver_connect)
254 || (c->type == CONNECT_TYPE_METASERVER && global->phase != PHASE_GETGAMES)
255 || (c->type == CONNECT_TYPE_MONOPD_GAME && global->game_connect)
256 ) {
257 client_connect_free(c);
258 return G_SOURCE_REMOVE;
259 }
260
261 channel = my_io_channel_socket_new(c->socket);
262 g_io_channel_set_encoding(channel, NULL, NULL);
263 c->channel = channel;
264 c->event_source_id = g_io_add_watch(channel, G_IO_IN|G_IO_ERR|G_IO_HUP, (GIOFunc)client_event, c);
265
266 msg = g_strdup_printf("Connected to %s:%d, using %s", c->host, c->port, c->ip);
267 interface_set_infolabel(msg, "008000", false);
268 g_free(msg);
269
270 if (c->buffer_out) {
271 client_send(c, c->buffer_out);
272 g_free(c->buffer_out);
273 c->buffer_out = NULL;
274 }
275
276 switch (c->type) {
277 case CONNECT_TYPE_MONOPD_GETGAME:
278 global->customserver_connect = c;
279 break;
280 case CONNECT_TYPE_MONOPD_GAME:
281 global->game_connect = c;
282 break;
283 case CONNECT_TYPE_METASERVER:
284 global->metaserver_connect = c;
285 break;
286 }
287
288 return G_SOURCE_REMOVE;
289 }
290
client_connect_failed(gchar * msg)291 static gboolean client_connect_failed(gchar *msg) {
292 interface_set_infolabel(msg, "b00000", true);
293 g_free(msg);
294 return G_SOURCE_REMOVE;
295 }
296
297 /* -- close the connection */
client_disconnect(connection * c)298 void client_disconnect(connection *c) {
299 if (!c) return;
300
301 switch (c->type) {
302 case CONNECT_TYPE_MONOPD_GETGAME:
303 global->customserver_connect = NULL;
304 break;
305 case CONNECT_TYPE_MONOPD_GAME:
306 global->game_connect = NULL;
307 break;
308 case CONNECT_TYPE_METASERVER:
309 global->metaserver_connect = NULL;
310 break;
311 }
312
313 client_connect_free(c);
314 }
315
client_connect_free(connection * c)316 static void client_connect_free(connection *c) {
317 if (c->event_source_id) g_source_remove(c->event_source_id);
318 if (c->channel) g_io_channel_unref(c->channel);
319 shutdown(c->socket, SHUT_RDWR);
320 closesocket(c->socket);
321 if (c->buffer_in) {
322 g_free(c->buffer_in);
323 }
324 g_free(c->host);
325 g_free(c->ip);
326 g_free(c->server_version);
327 g_free(c);
328 }
329
client_event(GIOChannel * channel,GIOCondition condition,connection * c)330 gboolean client_event(GIOChannel *channel, GIOCondition condition, connection *c) {
331
332 if (condition == G_IO_IN) {
333 GIOStatus status;
334 gchar chunk[1024 +1];
335 gsize bytes_read;
336 GError *err = NULL;
337 gchar *buffer, *beg, *cur;
338
339 status = g_io_channel_read_chars(channel, chunk, 1024, &bytes_read, &err);
340 switch (status) {
341 case G_IO_STATUS_NORMAL:
342 break;
343
344 case G_IO_STATUS_AGAIN:
345 return TRUE;
346
347 case G_IO_STATUS_ERROR:
348 case G_IO_STATUS_EOF:
349 client_disconnect(c);
350 interface_set_infolabel("Connection to server lost.", "b00000", true);
351 return TRUE;
352 }
353
354 chunk[bytes_read] = '\0';
355
356 if(c->buffer_in == NULL) {
357 buffer = g_strdup(chunk);
358 } else {
359 buffer = g_strconcat(c->buffer_in, chunk, NULL);
360 g_free(c->buffer_in);
361 c->buffer_in = NULL;
362 }
363
364 /* parse data */
365 for(beg = cur = buffer; *cur; cur++) {
366 if( *cur == '\n') {
367 *cur = '\0';
368 #if DEBUG
369 fprintf(stdout, "\033[32mID(%.2d): RECV(%.4d): [%s]\033[m\n", c->id, (int)strlen(beg), beg);
370 #endif /* DEBUG */
371 switch(c->type) {
372
373 case CONNECT_TYPE_MONOPD_GETGAME:
374 xmlparse_getgamelist_plugger(c, beg);
375 break;
376 case CONNECT_TYPE_MONOPD_GAME:
377 xmlparse_game_plugger(c, beg);
378 break;
379
380 case CONNECT_TYPE_METASERVER:
381 xmlparse_metaserver(c, beg);
382 break;
383 }
384
385 beg = cur+1;
386 }
387 }
388
389 /* bufferise remaining data */
390 if (beg != cur) {
391 c->buffer_in = g_strdup(beg);
392 }
393
394 g_free(buffer);
395 }
396
397 else /* if (condition == G_IO_ERR || condition == G_IO_HUP) */ {
398 client_disconnect(c);
399 interface_set_infolabel("Connection to server lost.", "b00000", true);
400 return TRUE;
401 }
402
403 return TRUE;
404 }
405
406
407 /* -- send data to server
408 >> get from send_tmp buffer
409 >> flush send_tmp buffer */
client_send(connection * c,gchar * data)410 void client_send(connection *c, gchar *data) {
411 size_t len;
412 ssize_t s;
413
414 if (!c) return;
415
416 len = strlen(data);
417 s = send(c->socket, data, len, 0);
418 if (s < 0 || (size_t)s != len) {
419 return;
420 }
421
422 #if DEBUG
423 fprintf(stdout, "\033[31mID(%.2d): SEND(%.4d): [%s]\033[m\n", c->id, (int)len, data);
424 #endif /* DEBUG */
425 }
426