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 port
= DEFAULT_SOCK_PORT
;
155 /* use name to find TCP/IP address of server */
157 hostname
= "localhost";
161 fc_sockaddr_list_destroy(list
);
164 /* Any supported family will do */
165 list
= net_lookup_service(hostname
, port
, FC_ADDR_ANY
);
167 name_count
= fc_sockaddr_list_size(list
);
169 if (name_count
<= 0) {
170 (void) fc_strlcpy(errbuf
, _("Failed looking up host."), errbufsize
);
177 /**************************************************************************
178 Try to connect to a server (get_server_address() must be called first!):
179 - try to create a TCP socket and connect it to `names'
181 - start monitoring the socket for packets from the server
182 - send a "login request" packet to the server
184 - if unable to create the connection, close the socket, put an error
185 message in ERRBUF and return the Unix error code (ie., errno, which
187 **************************************************************************/
188 static int try_to_connect(const char *username
, char *errbuf
, int errbufsize
)
193 connections_set_close_callback(client_conn_close_callback
);
195 /* connection in progress? wait. */
196 if (client
.conn
.used
) {
197 (void) fc_strlcpy(errbuf
, _("Connection in progress."), errbufsize
);
201 /* Try all (IPv4, IPv6, ...) addresses until we have a connection. */
203 fc_sockaddr_list_iterate(list
, paddr
) {
204 if ((sock
= socket(paddr
->saddr
.sa_family
, SOCK_STREAM
, 0)) == -1) {
206 err
= fc_get_errno();
208 /* Probably EAFNOSUPPORT or EPROTONOSUPPORT. */
212 if (fc_connect(sock
, &paddr
->saddr
,
213 sockaddr_size(paddr
)) == -1) {
214 err
= fc_get_errno(); /* Save errno value before calling anything */
215 fc_closesocket(sock
);
219 /* We have a connection! */
222 } fc_sockaddr_list_iterate_end
;
224 client
.conn
.sock
= sock
;
225 if (client
.conn
.sock
== -1) {
226 (void) fc_strlcpy(errbuf
, fc_strerror(err
), errbufsize
);
227 #ifdef FREECIV_HAVE_WINSOCK
231 #endif /* FREECIV_HAVE_WINSOCK */
234 make_connection(client
.conn
.sock
, username
);
239 /**************************************************************************
240 Connect to a freeciv-server instance -- or at least try to. On success,
241 return 0; on failure, put an error message in ERRBUF and return -1.
242 **************************************************************************/
243 int connect_to_server(const char *username
, const char *hostname
, int port
,
244 char *errbuf
, int errbufsize
)
246 if (errbufsize
> 0 && errbuf
!= NULL
) {
250 if (0 != get_server_address(hostname
, port
, errbuf
, errbufsize
)) {
254 if (0 != try_to_connect(username
, errbuf
, errbufsize
)) {
258 if (gui_options
.use_prev_server
) {
259 sz_strlcpy(gui_options
.default_server_host
, hostname
);
260 gui_options
.default_server_port
= port
;
266 /**************************************************************************
267 Called after a connection is completed (e.g., in try_to_connect).
268 **************************************************************************/
269 void make_connection(int sock
, const char *username
)
271 struct packet_server_join_req req
;
273 connection_common_init(&client
.conn
);
274 client
.conn
.sock
= sock
;
275 client
.conn
.client
.last_request_id_used
= 0;
276 client
.conn
.client
.last_processed_request_id_seen
= 0;
277 client
.conn
.client
.request_id_of_currently_handled_packet
= 0;
278 client
.conn
.incoming_packet_notify
= notify_about_incoming_packet
;
279 client
.conn
.outgoing_packet_notify
= notify_about_outgoing_packet
;
281 /* call gui-dependent stuff in gui_main.c */
282 add_net_input(client
.conn
.sock
);
284 /* now send join_request package */
286 req
.major_version
= MAJOR_VERSION
;
287 req
.minor_version
= MINOR_VERSION
;
288 req
.patch_version
= PATCH_VERSION
;
289 sz_strlcpy(req
.version_label
, VERSION_LABEL
);
290 sz_strlcpy(req
.capability
, our_capability
);
291 sz_strlcpy(req
.username
, username
);
293 send_packet_server_join_req(&client
.conn
, &req
);
296 /**************************************************************************
297 Get rid of server connection. This also kills internal server if it's
299 **************************************************************************/
300 void disconnect_from_server(void)
302 const bool force
= !client
.conn
.used
;
306 stop_turn_change_wait();
308 /* If it's internal server - kill him
309 * We assume that we are always connected to the internal server */
311 client_kill_server(FALSE
);
313 close_socket_nomessage(&client
.conn
);
315 client_kill_server(TRUE
);
317 output_window_append(ftc_client
, _("Disconnected from server."));
319 if (gui_options
.save_options_on_exit
) {
324 /****************************************************************************
325 A wrapper around read_socket_data() which also handles the case the
326 socket becomes writeable and there is still data which should be sent
330 -1 : an error occurred - you should close the socket
331 -2 : the connection was closed
332 >0 : number of bytes read
333 =0 : no data read, would block
334 ****************************************************************************/
335 static int read_from_connection(struct connection
*pc
, bool block
)
338 fd_set readfs
, writefs
, exceptfs
;
339 int socket_fd
= pc
->sock
;
340 bool have_data_for_server
= (pc
->used
&& pc
->send_buffer
341 && 0 < pc
->send_buffer
->ndata
);
349 FD_SET(socket_fd
, &readfs
);
351 FC_FD_ZERO(&exceptfs
);
352 FD_SET(socket_fd
, &exceptfs
);
354 if (have_data_for_server
) {
355 FC_FD_ZERO(&writefs
);
356 FD_SET(socket_fd
, &writefs
);
357 n
= fc_select(socket_fd
+ 1, &readfs
, &writefs
, &exceptfs
,
360 n
= fc_select(socket_fd
+ 1, &readfs
, NULL
, &exceptfs
,
364 /* the socket is neither readable, writeable nor got an
371 if (errno
== EINTR
) {
372 /* EINTR can happen sometimes, especially when compiling with -pg.
373 * Generally we just want to run select again. */
374 log_debug("select() returned EINTR");
378 log_error("select() return=%d errno=%d (%s)",
379 n
, errno
, fc_strerror(fc_get_errno()));
383 if (FD_ISSET(socket_fd
, &exceptfs
)) {
387 if (have_data_for_server
&& FD_ISSET(socket_fd
, &writefs
)) {
388 flush_connection_send_buffer_all(pc
);
391 if (FD_ISSET(socket_fd
, &readfs
)) {
392 return read_socket_data(socket_fd
, pc
->buffer
);
397 /**************************************************************************
398 This function is called when the client received a new input from the
400 **************************************************************************/
401 void input_from_server(int fd
)
405 fc_assert_ret(fd
== client
.conn
.sock
);
407 nb
= read_from_connection(&client
.conn
, FALSE
);
409 agents_freeze_hint();
410 while (client
.conn
.used
) {
411 enum packet_type type
;
412 void *packet
= get_packet_from_connection(&client
.conn
, &type
);
414 if (NULL
!= packet
) {
415 client_packet_input(packet
, type
);
421 if (client
.conn
.used
) {
424 } else if (-2 == nb
) {
425 connection_close(&client
.conn
, _("server disconnected"));
427 connection_close(&client
.conn
, _("read error"));
431 /**************************************************************************
432 This function will sniff at the given fd, get the packet and call
433 client_packet_input. It will return if there is a network error or if
434 the PACKET_PROCESSING_FINISHED packet for the given request is
436 **************************************************************************/
437 void input_from_server_till_request_got_processed(int fd
,
438 int expected_request_id
)
440 fc_assert_ret(expected_request_id
);
441 fc_assert_ret(fd
== client
.conn
.sock
);
443 log_debug("input_from_server_till_request_got_processed("
444 "expected_request_id=%d)", expected_request_id
);
447 int nb
= read_from_connection(&client
.conn
, TRUE
);
450 enum packet_type type
;
453 void *packet
= get_packet_from_connection(&client
.conn
, &type
);
454 if (NULL
== packet
) {
458 client_packet_input(packet
, type
);
461 if (type
== PACKET_PROCESSING_FINISHED
) {
462 log_debug("ifstrgp: expect=%d, seen=%d",
464 client
.conn
.client
.last_processed_request_id_seen
);
465 if (client
.conn
.client
.last_processed_request_id_seen
>=
466 expected_request_id
) {
467 log_debug("ifstrgp: got it; returning");
472 } else if (-2 == nb
) {
473 connection_close(&client
.conn
, _("server disconnected"));
476 connection_close(&client
.conn
, _("read error"));
482 static bool autoconnecting
= FALSE
;
483 /**************************************************************************
484 Make an attempt to autoconnect to the server.
485 It returns number of seconds it should be called again.
486 **************************************************************************/
487 double try_to_autoconnect(void)
490 static int count
= 0;
492 static int warning_shown
= 0;
495 if (!autoconnecting
) {
501 if (count
>= MAX_AUTOCONNECT_ATTEMPTS
) {
502 log_fatal(_("Failed to contact server \"%s\" at port "
503 "%d as \"%s\" after %d attempts"),
504 server_host
, server_port
, user_name
, count
);
508 switch (try_to_connect(user_name
, errbuf
, sizeof(errbuf
))) {
509 case 0: /* Success! */
510 /* Don't call me again */
511 autoconnecting
= FALSE
;
514 /* See PR#4042 for more info on issues with try_to_connect() and errno. */
515 case ECONNREFUSED
: /* Server not available (yet) */
516 if (!warning_shown
) {
517 log_error("Connection to server refused. Please start the server.");
518 output_window_append(ftc_client
, _("Connection to server refused. "
519 "Please start the server."));
522 /* Try again in 0.5 seconds */
523 return 0.001 * AUTOCONNECT_INTERVAL
;
524 #endif /* WIN32_NATIVE */
525 default: /* All other errors are fatal */
526 log_fatal(_("Error contacting server \"%s\" at port %d "
527 "as \"%s\":\n %s\n"),
528 server_host
, server_port
, user_name
, errbuf
);
533 /**************************************************************************
534 Start trying to autoconnect to freeciv-server. Calls
535 get_server_address(), then arranges for try_to_autoconnect(), which
536 calls try_to_connect(), to be called roughly every
537 AUTOCONNECT_INTERVAL milliseconds, until success, fatal error or
539 **************************************************************************/
540 void start_autoconnecting_to_server(void)
544 output_window_printf(ftc_client
,
545 _("Auto-connecting to server \"%s\" at port %d "
546 "as \"%s\" every %f second(s) for %d times"),
547 server_host
, server_port
, user_name
,
548 0.001 * AUTOCONNECT_INTERVAL
,
549 MAX_AUTOCONNECT_ATTEMPTS
);
551 if (get_server_address(server_host
, server_port
, buf
, sizeof(buf
)) < 0) {
552 log_fatal(_("Error contacting server \"%s\" at port %d "
553 "as \"%s\":\n %s\n"),
554 server_host
, server_port
, user_name
, buf
);
557 autoconnecting
= TRUE
;