1 /***********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
15 #include <fc_config.h>
18 #include "fc_prehdrs.h"
25 #ifdef FREECIV_HAVE_SYS_TYPES_H
26 #include <sys/types.h>
28 #ifdef HAVE_SYS_SOCKET_H
29 #include <sys/socket.h>
31 #ifdef HAVE_NETINET_IN_H
32 #include <netinet/in.h>
34 #ifdef HAVE_ARPA_INET_H
35 #include <arpa/inet.h>
43 #ifdef HAVE_SYS_SELECT_H
44 #include <sys/select.h>
46 #ifdef HAVE_SYS_TIME_H
52 #ifdef HAVE_SYS_UTSNAME_H
53 #include <sys/utsname.h>
76 #include "attribute.h"
77 #include "chatline_g.h"
78 #include "client_main.h"
80 #include "connectdlg_common.h"
81 #include "connectdlg_g.h"
82 #include "dialogs_g.h" /* popdown_races_dialog() */
83 #include "gui_main_g.h" /* add_net_input(), remove_net_input() */
84 #include "mapview_common.h" /* unqueue_mapview_update */
86 #include "messagewin_g.h"
91 #include "repodlgs_g.h"
95 /* In autoconnect mode, try to connect to once a second */
96 #define AUTOCONNECT_INTERVAL 500
98 /* In autoconnect mode, try to connect 100 times */
99 #define MAX_AUTOCONNECT_ATTEMPTS 100
101 static struct fc_sockaddr_list
*list
= NULL
;
102 static int name_count
;
104 /*************************************************************************
105 Close socket and cleanup. This one doesn't print a message, so should
106 do so before-hand if necessary.
107 **************************************************************************/
108 static void close_socket_nomessage(struct connection
*pc
)
110 connection_common_close(pc
);
112 popdown_races_dialog();
113 close_connection_dialog();
115 set_client_state(C_S_DISCONNECTED
);
118 /****************************************************************************
119 Client connection close socket callback. It shouldn't be called directy.
120 Use connection_close() instead.
121 ****************************************************************************/
122 static void client_conn_close_callback(struct connection
*pconn
)
126 if (NULL
!= pconn
->closing_reason
) {
127 fc_strlcpy(reason
, pconn
->closing_reason
, sizeof(reason
));
129 fc_strlcpy(reason
, _("unknown reason"), sizeof(reason
));
132 close_socket_nomessage(pconn
);
133 /* If we lost connection to the internal server - kill it. */
134 client_kill_server(TRUE
);
135 log_error("Lost connection to server: %s.", reason
);
136 output_window_printf(ftc_client
, _("Lost connection to server (%s)!"),
140 /**************************************************************************
141 Get ready to [try to] connect to a server:
142 - translate HOSTNAME and PORT (with defaults of "localhost" and
143 DEFAULT_SOCK_PORT respectively) to a raw IP address and port number, and
144 store them in the `names' variable
145 - return 0 on success
146 or put an error message in ERRBUF and return -1 on failure
147 **************************************************************************/
148 static int get_server_address(const char *hostname
, int port
,
149 char *errbuf
, int errbufsize
)
152 #ifdef FREECIV_JSON_CONNECTION
153 port
= FREECIV_JSON_PORT
;
154 #else /* FREECIV_JSON_CONNECTION */
155 port
= DEFAULT_SOCK_PORT
;
156 #endif /* FREECIV_JSON_CONNECTION */
159 /* use name to find TCP/IP address of server */
161 hostname
= "localhost";
165 fc_sockaddr_list_destroy(list
);
168 /* Any supported family will do */
169 list
= net_lookup_service(hostname
, port
, FC_ADDR_ANY
);
171 name_count
= fc_sockaddr_list_size(list
);
173 if (name_count
<= 0) {
174 (void) fc_strlcpy(errbuf
, _("Failed looking up host."), errbufsize
);
181 /**************************************************************************
182 Try to connect to a server (get_server_address() must be called first!):
183 - try to create a TCP socket and connect it to `names'
185 - start monitoring the socket for packets from the server
186 - send a "login request" packet to the server
188 - if unable to create the connection, close the socket, put an error
189 message in ERRBUF and return the Unix error code (ie., errno, which
191 **************************************************************************/
192 static int try_to_connect(const char *username
, char *errbuf
, int errbufsize
)
197 connections_set_close_callback(client_conn_close_callback
);
199 /* connection in progress? wait. */
200 if (client
.conn
.used
) {
201 (void) fc_strlcpy(errbuf
, _("Connection in progress."), errbufsize
);
205 /* Try all (IPv4, IPv6, ...) addresses until we have a connection. */
207 fc_sockaddr_list_iterate(list
, paddr
) {
208 if ((sock
= socket(paddr
->saddr
.sa_family
, SOCK_STREAM
, 0)) == -1) {
210 err
= fc_get_errno();
212 /* Probably EAFNOSUPPORT or EPROTONOSUPPORT. */
216 if (fc_connect(sock
, &paddr
->saddr
,
217 sockaddr_size(paddr
)) == -1) {
218 err
= fc_get_errno(); /* Save errno value before calling anything */
219 fc_closesocket(sock
);
223 /* We have a connection! */
226 } fc_sockaddr_list_iterate_end
;
228 client
.conn
.sock
= sock
;
229 if (client
.conn
.sock
== -1) {
230 (void) fc_strlcpy(errbuf
, fc_strerror(err
), errbufsize
);
231 #ifdef FREECIV_HAVE_WINSOCK
235 #endif /* FREECIV_HAVE_WINSOCK */
238 make_connection(client
.conn
.sock
, username
);
243 /**************************************************************************
244 Connect to a freeciv-server instance -- or at least try to. On success,
245 return 0; on failure, put an error message in ERRBUF and return -1.
246 **************************************************************************/
247 int connect_to_server(const char *username
, const char *hostname
, int port
,
248 char *errbuf
, int errbufsize
)
250 if (errbufsize
> 0 && errbuf
!= NULL
) {
254 if (0 != get_server_address(hostname
, port
, errbuf
, errbufsize
)) {
258 if (0 != try_to_connect(username
, errbuf
, errbufsize
)) {
262 if (gui_options
.use_prev_server
) {
263 sz_strlcpy(gui_options
.default_server_host
, hostname
);
264 gui_options
.default_server_port
= port
;
270 /**************************************************************************
271 Called after a connection is completed (e.g., in try_to_connect).
272 **************************************************************************/
273 void make_connection(int sock
, const char *username
)
275 struct packet_server_join_req req
;
277 connection_common_init(&client
.conn
);
278 client
.conn
.sock
= sock
;
279 client
.conn
.client
.last_request_id_used
= 0;
280 client
.conn
.client
.last_processed_request_id_seen
= 0;
281 client
.conn
.client
.request_id_of_currently_handled_packet
= 0;
282 client
.conn
.incoming_packet_notify
= notify_about_incoming_packet
;
283 client
.conn
.outgoing_packet_notify
= notify_about_outgoing_packet
;
285 /* call gui-dependent stuff in gui_main.c */
286 add_net_input(client
.conn
.sock
);
288 /* now send join_request package */
290 req
.major_version
= MAJOR_VERSION
;
291 req
.minor_version
= MINOR_VERSION
;
292 req
.patch_version
= PATCH_VERSION
;
293 sz_strlcpy(req
.version_label
, VERSION_LABEL
);
294 sz_strlcpy(req
.capability
, our_capability
);
295 sz_strlcpy(req
.username
, username
);
297 send_packet_server_join_req(&client
.conn
, &req
);
300 /**************************************************************************
301 Get rid of server connection. This also kills internal server if it's
303 **************************************************************************/
304 void disconnect_from_server(void)
306 const bool force
= !client
.conn
.used
;
310 stop_turn_change_wait();
312 /* If it's internal server - kill him
313 * We assume that we are always connected to the internal server */
315 client_kill_server(FALSE
);
317 close_socket_nomessage(&client
.conn
);
319 client_kill_server(TRUE
);
321 output_window_append(ftc_client
, _("Disconnected from server."));
323 if (gui_options
.save_options_on_exit
) {
328 /****************************************************************************
329 A wrapper around read_socket_data() which also handles the case the
330 socket becomes writeable and there is still data which should be sent
334 -1 : an error occurred - you should close the socket
335 -2 : the connection was closed
336 >0 : number of bytes read
337 =0 : no data read, would block
338 ****************************************************************************/
339 static int read_from_connection(struct connection
*pc
, bool block
)
342 fd_set readfs
, writefs
, exceptfs
;
343 int socket_fd
= pc
->sock
;
344 bool have_data_for_server
= (pc
->used
&& pc
->send_buffer
345 && 0 < pc
->send_buffer
->ndata
);
353 FD_SET(socket_fd
, &readfs
);
355 FC_FD_ZERO(&exceptfs
);
356 FD_SET(socket_fd
, &exceptfs
);
358 if (have_data_for_server
) {
359 FC_FD_ZERO(&writefs
);
360 FD_SET(socket_fd
, &writefs
);
361 n
= fc_select(socket_fd
+ 1, &readfs
, &writefs
, &exceptfs
,
364 n
= fc_select(socket_fd
+ 1, &readfs
, NULL
, &exceptfs
,
368 /* the socket is neither readable, writeable nor got an
375 if (errno
== EINTR
) {
376 /* EINTR can happen sometimes, especially when compiling with -pg.
377 * Generally we just want to run select again. */
378 log_debug("select() returned EINTR");
382 log_error("select() return=%d errno=%d (%s)",
383 n
, errno
, fc_strerror(fc_get_errno()));
387 if (FD_ISSET(socket_fd
, &exceptfs
)) {
391 if (have_data_for_server
&& FD_ISSET(socket_fd
, &writefs
)) {
392 flush_connection_send_buffer_all(pc
);
395 if (FD_ISSET(socket_fd
, &readfs
)) {
396 return read_socket_data(socket_fd
, pc
->buffer
);
401 /**************************************************************************
402 This function is called when the client received a new input from the
404 **************************************************************************/
405 void input_from_server(int fd
)
409 fc_assert_ret(fd
== client
.conn
.sock
);
411 nb
= read_from_connection(&client
.conn
, FALSE
);
413 agents_freeze_hint();
414 while (client
.conn
.used
) {
415 enum packet_type type
;
416 void *packet
= get_packet_from_connection(&client
.conn
, &type
);
418 if (NULL
!= packet
) {
419 client_packet_input(packet
, type
);
425 if (client
.conn
.used
) {
428 } else if (-2 == nb
) {
429 connection_close(&client
.conn
, _("server disconnected"));
431 connection_close(&client
.conn
, _("read error"));
435 /**************************************************************************
436 This function will sniff at the given fd, get the packet and call
437 client_packet_input. It will return if there is a network error or if
438 the PACKET_PROCESSING_FINISHED packet for the given request is
440 **************************************************************************/
441 void input_from_server_till_request_got_processed(int fd
,
442 int expected_request_id
)
444 fc_assert_ret(expected_request_id
);
445 fc_assert_ret(fd
== client
.conn
.sock
);
447 log_debug("input_from_server_till_request_got_processed("
448 "expected_request_id=%d)", expected_request_id
);
451 int nb
= read_from_connection(&client
.conn
, TRUE
);
454 enum packet_type type
;
457 void *packet
= get_packet_from_connection(&client
.conn
, &type
);
458 if (NULL
== packet
) {
462 client_packet_input(packet
, type
);
465 if (type
== PACKET_PROCESSING_FINISHED
) {
466 log_debug("ifstrgp: expect=%d, seen=%d",
468 client
.conn
.client
.last_processed_request_id_seen
);
469 if (client
.conn
.client
.last_processed_request_id_seen
>=
470 expected_request_id
) {
471 log_debug("ifstrgp: got it; returning");
476 } else if (-2 == nb
) {
477 connection_close(&client
.conn
, _("server disconnected"));
480 connection_close(&client
.conn
, _("read error"));
486 static bool autoconnecting
= FALSE
;
487 /**************************************************************************
488 Make an attempt to autoconnect to the server.
489 It returns number of seconds it should be called again.
490 **************************************************************************/
491 double try_to_autoconnect(void)
494 static int count
= 0;
495 #ifndef FREECIV_MSWINDOWS
496 static int warning_shown
= 0;
499 if (!autoconnecting
) {
505 if (count
>= MAX_AUTOCONNECT_ATTEMPTS
) {
506 log_fatal(_("Failed to contact server \"%s\" at port "
507 "%d as \"%s\" after %d attempts"),
508 server_host
, server_port
, user_name
, count
);
512 switch (try_to_connect(user_name
, errbuf
, sizeof(errbuf
))) {
513 case 0: /* Success! */
514 /* Don't call me again */
515 autoconnecting
= FALSE
;
517 #ifndef FREECIV_MSWINDOWS
518 /* See PR#4042 for more info on issues with try_to_connect() and errno. */
519 case ECONNREFUSED
: /* Server not available (yet) */
520 if (!warning_shown
) {
521 log_error("Connection to server refused. Please start the server.");
522 output_window_append(ftc_client
, _("Connection to server refused. "
523 "Please start the server."));
526 /* Try again in 0.5 seconds */
527 return 0.001 * AUTOCONNECT_INTERVAL
;
528 #endif /* FREECIV_MSWINDOWS */
529 default: /* All other errors are fatal */
530 log_fatal(_("Error contacting server \"%s\" at port %d "
531 "as \"%s\":\n %s\n"),
532 server_host
, server_port
, user_name
, errbuf
);
537 /**************************************************************************
538 Start trying to autoconnect to freeciv-server. Calls
539 get_server_address(), then arranges for try_to_autoconnect(), which
540 calls try_to_connect(), to be called roughly every
541 AUTOCONNECT_INTERVAL milliseconds, until success, fatal error or
543 **************************************************************************/
544 void start_autoconnecting_to_server(void)
548 output_window_printf(ftc_client
,
549 _("Auto-connecting to server \"%s\" at port %d "
550 "as \"%s\" every %f second(s) for %d times"),
551 server_host
, server_port
, user_name
,
552 0.001 * AUTOCONNECT_INTERVAL
,
553 MAX_AUTOCONNECT_ATTEMPTS
);
555 if (get_server_address(server_host
, server_port
, buf
, sizeof(buf
)) < 0) {
556 log_fatal(_("Error contacting server \"%s\" at port %d "
557 "as \"%s\":\n %s\n"),
558 server_host
, server_port
, user_name
, buf
);
561 autoconnecting
= TRUE
;