44 case SERVER_ADDRESS_DIRECT:
45 this->connection_string = this->server_address.connection_string;
48 case SERVER_ADDRESS_INVITE_CODE:
49 this->status = Status::Connecting;
50 _network_coordinator_client.ConnectToServer(this->server_address.connection_string, this);
58TCPConnecter::~TCPConnecter()
64 for (
const auto &socket : this->
sockets) {
70 if (this->
ai !=
nullptr) freeaddrinfo(this->
ai);
89 SOCKET sock = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
90 if (sock == INVALID_SOCKET) {
100 if (bind(sock, (
const sockaddr *)this->
bind_address.GetAddress(), this->bind_address.GetAddressLength()) != 0) {
117 int err = connect(sock, address->ai_addr, (
int)address->ai_addrlen);
126 this->sockets.push_back(sock);
149 std::deque<addrinfo *> addresses_ipv4, addresses_ipv6;
154 bool seen_ipv6 =
false;
156 for (addrinfo *runp =
ai; runp !=
nullptr; runp = runp->ai_next) {
157 if (runp->ai_family == AF_INET6) {
159 }
else if (!seen_ipv6) {
169 for (addrinfo *runp =
ai; runp !=
nullptr; runp = runp->ai_next) {
171 if (this->
family != AF_UNSPEC && this->
family != runp->ai_family)
continue;
174 if (runp->ai_family == AF_INET6) {
175 addresses_ipv6.emplace_back(runp);
177 addresses_ipv4.emplace_back(runp);
187 while (!addresses_ipv4.empty() || !addresses_ipv6.empty()) {
188 if (!addresses_ipv6.empty()) {
189 this->
addresses.push_back(addresses_ipv6.front());
190 addresses_ipv6.pop_front();
192 if (!addresses_ipv4.empty()) {
193 this->
addresses.push_back(addresses_ipv4.front());
194 addresses_ipv4.pop_front();
199 if (_debug_net_level >= 6) {
204 for (
const auto &address : this->
addresses) {
225 hints.ai_family = AF_UNSPEC;
226 hints.ai_flags = AI_ADDRCONFIG;
227 hints.ai_socktype = SOCK_STREAM;
229 std::string port_name = fmt::format(
"{}", address.
GetPort());
231 static bool getaddrinfo_timeout_error_shown =
false;
232 auto start = std::chrono::steady_clock::now();
235 int error = getaddrinfo(address.
GetHostname().c_str(), port_name.c_str(), &hints, &
ai);
237 auto end = std::chrono::steady_clock::now();
238 auto duration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
239 if (!getaddrinfo_timeout_error_shown && duration >= std::chrono::seconds(5)) {
240 Debug(net, 0,
"getaddrinfo() for address \"{}\" took {} seconds", this->
connection_string, duration.count());
241 Debug(net, 0,
" This is likely an issue in the DNS name resolver's configuration causing it to time out");
242 getaddrinfo_timeout_error_shown =
true;
271 if (this->
killed)
return true;
305 if (this->sockets.empty()) {
316 for (
const auto &socket : this->sockets) {
317 FD_SET(socket, &write_fd);
323 int n = select(FD_SETSIZE,
nullptr, &write_fd,
nullptr, &tv);
334 if (std::chrono::steady_clock::now() < this->
last_attempt + std::chrono::milliseconds(250))
return false;
340 if (std::chrono::steady_clock::now() < this->
last_attempt + std::chrono::milliseconds(3000))
return false;
346 for (
const auto &socket : this->sockets) {
349 this->sockets.clear();
359 SOCKET connected_socket = INVALID_SOCKET;
360 for (
auto it = this->sockets.begin(); it != this->sockets.end(); ) {
366 it = this->sockets.erase(it);
371 if (connected_socket == INVALID_SOCKET && FD_ISSET(*it, &write_fd)) {
372 connected_socket = *it;
379 if (connected_socket == INVALID_SOCKET)
return false;
382 for (
auto it = this->sockets.begin(); it != this->sockets.end(); ) {
383 if (connected_socket != *it) {
387 it = this->sockets.erase(it);
391 if (_debug_net_level >= 5) {
406 if (this->
killed)
return true;
441 assert(sock != INVALID_SOCKET);
465 [](
auto &connecter) {
return connecter->CheckActivity(); }),
@ SERVER_ADDRESS_INVITE_CODE
Server-address is based on an invite code.
@ SERVER_ADDRESS_DIRECT
Server-address is based on an hostname:port.
Wrapper for (un)resolved network addresses; there's no reason to transform a numeric IP to a string a...
static std::string_view AddressFamilyAsString(int family)
Convert the address family into a string.
static const std::string GetPeerName(SOCKET sock)
Get the peer name of a socket in string format.
static std::string_view SocketTypeAsString(int socktype)
Convert the socket type into a string.
uint16_t GetPort() const
Get the port.
const std::string & GetHostname()
Get the hostname; in case it wasn't given the IPv4 dotted representation is given.
std::string GetAddressAsString(bool with_family=true)
Get the address as a string, e.g.
Abstraction of a network error where all implementation details of the error codes are encapsulated i...
std::string_view AsString() const
Get the string representation of the error message.
bool HasError() const
Check whether an error was actually set.
static NetworkError GetLast()
Get the last network error.
Address to a game server.
NetworkAddress bind_address
Address we're binding to, if any.
void Kill()
Kill this connecter.
std::chrono::steady_clock::time_point last_attempt
Time we last tried to connect.
std::atomic< Status > status
The current status of the connecter.
std::string connection_string
Current address we are connecting to (before resolving).
static std::vector< std::shared_ptr< TCPConnecter > > connecters
List of connections that are currently being created.
std::vector< SOCKET > sockets
Pending connect() attempts.
static void CheckCallbacks()
Check whether we need to call the callback, i.e.
size_t current_address
Current index in addresses we are trying.
void Resolve()
Start resolving the hostname.
virtual void OnFailure()
Callback for when the connection attempt failed.
void OnResolved(addrinfo *ai)
Callback when resolving is done.
virtual bool CheckActivity()
Check if there was activity for this connecter.
std::map< SOCKET, NetworkAddress > sock_to_address
Mapping of a socket to the real address it is connecting to. USed for DEBUG statements.
static void ResolveThunk(TCPConnecter *connecter)
Thunk to start Resolve() on the right instance.
std::vector< addrinfo * > addresses
Addresses we can connect to.
std::thread resolve_thread
Thread used during resolving.
static void KillAll()
Kill all connection attempts.
bool TryNextAddress()
Start the connect() for the next address in the list.
std::atomic< bool > killed
Whether this connecter is marked as killed.
@ Connected
The connection is established.
@ Resolving
The hostname is being resolved (threaded).
@ Init
TCPConnecter is created but resolving hasn't started.
@ Failure
Resolving failed.
@ Connecting
We are currently connecting.
virtual void OnConnect(SOCKET s)
Callback when the connection succeeded.
void Connect(addrinfo *address)
Start a connection to the indicated address.
addrinfo * ai
getaddrinfo() allocated linked-list of resolved addresses.
int family
Family we are using to connect with.
void SetConnected(SOCKET sock)
The connection was successfully established.
bool CheckActivity() override
Check if there was activity for this connecter.
ServerAddress server_address
Address we are connecting to.
void SetFailure()
The connection couldn't be established.
TCPServerConnecter(std::string_view connection_string, uint16_t default_port)
Create a new connecter for the server.
SOCKET socket
The socket when a connection is established.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
NetworkAddress ParseConnectionString(std::string_view connection_string, uint16_t default_port)
Convert a string containing either "hostname" or "hostname:ip" to a NetworkAddress.
std::string NormalizeConnectionString(std::string_view connection_string, uint16_t default_port)
Normalize a connection string.
Part of the network protocol handling Game Coordinator requests.
Variables and function used internally.
bool SetReusePort(SOCKET d)
Try to set the socket to reuse ports.
bool SetNoDelay(SOCKET d)
Try to set the socket to not delay sending.
bool SetNonBlocking(SOCKET d)
Try to set the socket into non-blocking mode.
NetworkError GetSocketError(SOCKET d)
Get the error from a socket, if any.
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
Basic functions to receive and send TCP packets.
bool StartNewThread(std::thread *thr, std::string_view name, TFn &&_Fx, TArgs &&... _Ax)
Start a new thread.