Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] [Net] Remove blocking network address resolution in connect_to_host in websockets #53243

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 102 additions & 55 deletions modules/websocket/wsl_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,71 +157,35 @@ bool WSLClient::_verify_headers(String &r_protocol) {
}

Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocols, const Vector<String> p_custom_headers) {
ERR_FAIL_COND_V(_connection.is_valid(), ERR_ALREADY_IN_USE);
ERR_FAIL_COND_V(_status != STATUS_DISCONNECTED, ERR_ALREADY_IN_USE);
ERR_FAIL_COND_V(p_path.empty(), ERR_INVALID_PARAMETER);

_peer = Ref<WSLPeer>(memnew(WSLPeer));

if (p_host.is_valid_ip_address()) {
ip_candidates.clear();
ip_candidates.push_back(IP_Address(p_host));
} else {
ip_candidates = IP::get_singleton()->resolve_hostname_addresses(p_host);
}

ERR_FAIL_COND_V(ip_candidates.empty(), ERR_INVALID_PARAMETER);

String port = "";
if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) {
port = ":" + itos(p_port);
}

Error err = ERR_BUG; // Should be at least one entry.
while (ip_candidates.size() > 0) {
err = _tcp->connect_to_host(ip_candidates.pop_front(), p_port);
if (err == OK) {
break;
}
}
if (err != OK) {
_tcp->disconnect_from_host();
_on_error();
return err;
}
_connection = _tcp;
_use_ssl = p_ssl;
_host = p_host;
_port = p_port;
_path = p_path;
_use_ssl = p_ssl;
_original_protocols = p_protocols;
// Strip edges from protocols.
_protocols.resize(p_protocols.size());
String *pw = _protocols.ptrw();
for (int i = 0; i < p_protocols.size(); i++) {
pw[i] = p_protocols[i].strip_edges();
}
_custom_headers = p_custom_headers;

_key = WSLPeer::generate_key();
// TODO custom extra headers (allow overriding this too?)
String request = "GET " + p_path + " HTTP/1.1\r\n";
request += "Host: " + p_host + port + "\r\n";
request += "Upgrade: websocket\r\n";
request += "Connection: Upgrade\r\n";
request += "Sec-WebSocket-Key: " + _key + "\r\n";
request += "Sec-WebSocket-Version: 13\r\n";
if (p_protocols.size() > 0) {
request += "Sec-WebSocket-Protocol: ";
for (int i = 0; i < p_protocols.size(); i++) {
if (i != 0) {
request += ",";
}
request += p_protocols[i];
}
request += "\r\n";
}
for (int i = 0; i < p_custom_headers.size(); i++) {
request += p_custom_headers[i] + "\r\n";
if (p_host.is_valid_ip_address()) {
IP_Address addr = IP_Address(p_host);
ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER);
_ip_candidates.clear();
_ip_candidates.push_back(addr);
_status = Status::STATUS_RESOLVED;
} else {
// Host contains hostname and needs to be resolved to IP
_resolving = IP::get_singleton()->resolve_hostname_queue_item(p_host);
_status = Status::STATUS_RESOLVING;
}
request += "\r\n";
_request = request.utf8();

return OK;
}
Expand All @@ -231,6 +195,84 @@ int WSLClient::get_max_packet_size() const {
}

void WSLClient::poll() {
if (_status == Status::STATUS_RESOLVING) {
if (_resolving == IP::RESOLVER_INVALID_ID) {
_status = Status::STATUS_DISCONNECTED;
_on_error();
ERR_FAIL_MSG("Bug, invalid resolver ID");
}

IP::ResolverStatus rstatus = IP::get_singleton()->get_resolve_item_status(_resolving);
switch (rstatus) {
case IP::RESOLVER_STATUS_WAITING:
return; // Still resolving

case IP::RESOLVER_STATUS_DONE: {
_ip_candidates = IP::get_singleton()->get_resolve_item_addresses(_resolving);

if (_ip_candidates.size() == 0) {
_status = Status::STATUS_DISCONNECTED;
_on_error();
ERR_FAIL_MSG("Error, zero resolved hostnames");
}
_status = Status::STATUS_RESOLVED;
} break;
case IP::RESOLVER_STATUS_NONE:
case IP::RESOLVER_STATUS_ERROR: {
_status = Status::STATUS_DISCONNECTED;
_on_error();
ERR_FAIL_MSG("Error, couldn't resolve hostname");
} break;
}
}

if (_status == Status::STATUS_RESOLVED) {
String port = "";
if ((_port != 80 && !_use_ssl) || (_port != 443 && _use_ssl)) {
port = ":" + itos(_port);
}

Error err = ERR_BUG; // Should be at least one entry.
while (_ip_candidates.size() > 0) {
err = _tcp->connect_to_host(_ip_candidates.pop_front(), _port);
if (err == OK) {
break;
}
}
if (err != OK) {
_status = Status::STATUS_DISCONNECTED;
_tcp->disconnect_from_host();
_on_error();
return;
}
_connection = _tcp;

_key = WSLPeer::generate_key();
// TODO custom extra headers (allow overriding this too?)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this TODO since we now support extra headers (was a leftover from the other PR I guess).

String request = "GET " + _path + " HTTP/1.1\r\n";
request += "Host: " + _host + port + "\r\n";
request += "Upgrade: websocket\r\n";
request += "Connection: Upgrade\r\n";
request += "Sec-WebSocket-Key: " + _key + "\r\n";
request += "Sec-WebSocket-Version: 13\r\n";
if (_original_protocols.size() > 0) {
request += "Sec-WebSocket-Protocol: ";
for (int i = 0; i < _original_protocols.size(); i++) {
if (i != 0)
request += ",";
request += _original_protocols[i];
}
request += "\r\n";
}
for (int i = 0; i < _custom_headers.size(); i++) {
request += _custom_headers[i] + "\r\n";
}
request += "\r\n";
_request = request.utf8();

_status = STATUS_CONNECTED;
}

if (_peer->is_connected_to_host()) {
_peer->poll();
if (!_peer->is_connected_to_host()) {
Expand All @@ -251,7 +293,7 @@ void WSLClient::poll() {
_on_error();
break;
case StreamPeerTCP::STATUS_CONNECTED: {
ip_candidates.clear();
_ip_candidates.clear();
Ref<StreamPeerSSL> ssl;
if (_use_ssl) {
if (_connection == _tcp) {
Expand Down Expand Up @@ -282,9 +324,9 @@ void WSLClient::poll() {
_do_handshake();
} break;
case StreamPeerTCP::STATUS_ERROR:
while (ip_candidates.size() > 0) {
while (_ip_candidates.size() > 0) {
_tcp->disconnect_from_host();
if (_tcp->connect_to_host(ip_candidates.pop_front(), _port) == OK) {
if (_tcp->connect_to_host(_ip_candidates.pop_front(), _port) == OK) {
return;
}
}
Expand All @@ -303,6 +345,10 @@ Ref<WebSocketPeer> WSLClient::get_peer(int p_peer_id) const {
}

NetworkedMultiplayerPeer::ConnectionStatus WSLClient::get_connection_status() const {
if (_status == Status::STATUS_RESOLVING || _status == Status::STATUS_RESOLVED) {
return CONNECTION_CONNECTING;
}

if (_peer->is_connected_to_host()) {
return CONNECTION_CONNECTED;
}
Expand All @@ -315,6 +361,7 @@ NetworkedMultiplayerPeer::ConnectionStatus WSLClient::get_connection_status() co
}

void WSLClient::disconnect_from_host(int p_code, String p_reason) {
_status = Status::STATUS_DISCONNECTED;
_peer->close(p_code, p_reason);
_connection = Ref<StreamPeer>(nullptr);
_tcp = Ref<StreamPeerTCP>(memnew(StreamPeerTCP));
Expand All @@ -330,7 +377,7 @@ void WSLClient::disconnect_from_host(int p_code, String p_reason) {
memset(_resp_buf, 0, sizeof(_resp_buf));
_resp_pos = 0;

ip_candidates.clear();
_ip_candidates.clear();
}

IP_Address WSLClient::get_connected_host() const {
Expand Down
17 changes: 15 additions & 2 deletions modules/websocket/wsl_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@
class WSLClient : public WebSocketClient {
GDCIIMPL(WSLClient, WebSocketClient);

public:
enum Status {
STATUS_DISCONNECTED,
STATUS_RESOLVING,
STATUS_RESOLVED,
STATUS_CONNECTED
};

private:
int _in_buf_size;
int _in_pkt_size;
Expand All @@ -63,10 +71,15 @@ class WSLClient : public WebSocketClient {

String _key;
String _host;
int _port;
Array ip_candidates;
uint16_t _port;
String _path;
Array _ip_candidates;
Vector<String> _protocols;
Vector<String> _original_protocols;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why we need the _original_protocols.
It seems there is already the _protocols variable that could be used.

Vector<String> _custom_headers;
bool _use_ssl;
Status _status;
IP::ResolverID _resolving;

void _do_handshake();
bool _verify_headers(String &r_protocol);
Expand Down