From 7227fdd805a8def84902e0b8e90664b0c2e44536 Mon Sep 17 00:00:00 2001 From: Stuart Carnie Date: Wed, 25 Jun 2025 07:01:29 +1000 Subject: [PATCH] Core: Add UNIX domain socket support > [!NOTE] > > Later versions of Windows has support for `AF_UNIX`, so it could be > added. --- core/debugger/engine_debugger.cpp | 13 +- core/debugger/remote_debugger_peer.cpp | 67 ++-- core/debugger/remote_debugger_peer.h | 12 +- core/io/net_socket.h | 50 ++- core/io/packet_peer_udp.cpp | 20 +- core/io/socket_server.cpp | 90 ++++++ core/io/socket_server.h | 77 +++++ core/io/stream_peer_socket.compat.inc | 52 +++ core/io/stream_peer_socket.cpp | 236 ++++++++++++++ core/io/stream_peer_socket.h | 93 ++++++ core/io/stream_peer_tcp.cpp | 231 +------------- core/io/stream_peer_tcp.h | 46 +-- core/io/stream_peer_uds.cpp | 99 ++++++ core/io/stream_peer_uds.h | 48 +++ core/io/tcp_server.cpp | 75 +---- core/io/tcp_server.h | 20 +- core/io/udp_server.cpp | 11 +- core/io/uds_server.cpp | 52 +++ core/io/uds_server.h | 46 +++ core/register_core_types.cpp | 8 + doc/classes/ProjectSettings.xml | 3 + doc/classes/SocketServer.xml | 37 +++ doc/classes/StreamPeerSocket.xml | 45 +++ doc/classes/StreamPeerTCP.xml | 34 +- doc/classes/StreamPeerUDS.xml | 35 ++ doc/classes/TCPServer.xml | 20 +- doc/classes/UDSServer.xml | 28 ++ drivers/unix/net_socket_unix.cpp | 276 ++++++++++++++-- drivers/unix/net_socket_unix.h | 28 +- drivers/windows/net_socket_winsock.cpp | 33 +- drivers/windows/net_socket_winsock.h | 10 +- editor/debugger/editor_debugger_node.cpp | 3 +- editor/debugger/editor_debugger_server.cpp | 79 +++-- editor/run/editor_run_bar.cpp | 8 +- .../4.5-stable.expected | 12 + platform/web/net_socket_web.h | 10 +- tests/core/io/test_uds_server.h | 302 ++++++++++++++++++ tests/test_main.cpp | 1 + thirdparty/enet/enet_godot.cpp | 10 +- 39 files changed, 1791 insertions(+), 529 deletions(-) create mode 100644 core/io/socket_server.cpp create mode 100644 core/io/socket_server.h create mode 100644 core/io/stream_peer_socket.compat.inc create mode 100644 core/io/stream_peer_socket.cpp create mode 100644 core/io/stream_peer_socket.h create mode 100644 core/io/stream_peer_uds.cpp create mode 100644 core/io/stream_peer_uds.h create mode 100644 core/io/uds_server.cpp create mode 100644 core/io/uds_server.h create mode 100644 doc/classes/SocketServer.xml create mode 100644 doc/classes/StreamPeerSocket.xml create mode 100644 doc/classes/StreamPeerUDS.xml create mode 100644 doc/classes/UDSServer.xml create mode 100644 tests/core/io/test_uds_server.h diff --git a/core/debugger/engine_debugger.cpp b/core/debugger/engine_debugger.cpp index 2035ab08afe..ee0a06a0c34 100644 --- a/core/debugger/engine_debugger.cpp +++ b/core/debugger/engine_debugger.cpp @@ -121,7 +121,10 @@ void EngineDebugger::iteration(uint64_t p_frame_ticks, uint64_t p_process_ticks, } void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, bool p_ignore_error_breaks, const Vector &p_breakpoints, void (*p_allow_focus_steal_fn)()) { - register_uri_handler("tcp://", RemoteDebuggerPeerTCP::create); // TCP is the default protocol. Platforms/modules can add more. + register_uri_handler("tcp://", RemoteDebuggerPeerTCP::create_tcp); // TCP is the default protocol. Platforms/modules can add more. +#ifdef UNIX_ENABLED + register_uri_handler("unix://", RemoteDebuggerPeerTCP::create_unix); +#endif if (p_uri.is_empty()) { return; } @@ -132,10 +135,10 @@ void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, bo OS::get_singleton()->initialize_debugging(); } else if (p_uri.contains("://")) { const String proto = p_uri.substr(0, p_uri.find("://") + 3); - if (!protocols.has(proto)) { - return; - } - RemoteDebuggerPeer *peer = protocols[proto](p_uri); + CreatePeerFunc *create_fn = protocols.getptr(proto); + ERR_FAIL_NULL_MSG(create_fn, vformat("Invalid protocol: %s.", proto)); + + RemoteDebuggerPeer *peer = (*create_fn)(p_uri); if (!peer) { return; } diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp index 536a83f67c3..759394a2a54 100644 --- a/core/debugger/remote_debugger_peer.cpp +++ b/core/debugger/remote_debugger_peer.cpp @@ -76,18 +76,19 @@ void RemoteDebuggerPeerTCP::close() { in_buf.clear(); } -RemoteDebuggerPeerTCP::RemoteDebuggerPeerTCP(Ref p_tcp) { +RemoteDebuggerPeerTCP::RemoteDebuggerPeerTCP() { // This means remote debugger takes 16 MiB just because it exists... in_buf.resize((8 << 20) + 4); // 8 MiB should be way more than enough (need 4 extra bytes for encoding packet size). out_buf.resize(8 << 20); // 8 MiB should be way more than enough - tcp_client = p_tcp; - if (tcp_client.is_valid()) { // Attaching to an already connected stream. - connected = true; - running = true; - thread.start(_thread_func, this); - } else { - tcp_client.instantiate(); - } +} + +RemoteDebuggerPeerTCP::RemoteDebuggerPeerTCP(Ref p_stream) : + RemoteDebuggerPeerTCP() { + DEV_ASSERT(p_stream.is_valid()); + tcp_client = p_stream; + connected = true; + running = true; + thread.start(_thread_func, this); } RemoteDebuggerPeerTCP::~RemoteDebuggerPeerTCP() { @@ -154,22 +155,10 @@ void RemoteDebuggerPeerTCP::_read_in() { } } -Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_port) { - IPAddress ip; - if (p_host.is_valid_ip_address()) { - ip = p_host; - } else { - ip = IP::get_singleton()->resolve_hostname(p_host); - } - - int port = p_port; - +Error RemoteDebuggerPeerTCP::_try_connect(Ref tcp_client) { const int tries = 6; const int waits[tries] = { 1, 10, 100, 1000, 1000, 1000 }; - Error err = tcp_client->connect_to_host(ip, port); - ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Remote Debugger: Unable to connect to host '%s:%d'.", p_host, port)); - for (int i = 0; i < tries; i++) { tcp_client->poll(); if (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED) { @@ -186,9 +175,6 @@ Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_po ERR_PRINT(vformat("Remote Debugger: Unable to connect. Status: %s.", String::num_int64(tcp_client->get_status()))); return FAILED; } - connected = true; - running = true; - thread.start(_thread_func, this); return OK; } @@ -222,7 +208,7 @@ void RemoteDebuggerPeerTCP::_poll() { } } -RemoteDebuggerPeer *RemoteDebuggerPeerTCP::create(const String &p_uri) { +RemoteDebuggerPeer *RemoteDebuggerPeerTCP::create_tcp(const String &p_uri) { ERR_FAIL_COND_V(!p_uri.begins_with("tcp://"), nullptr); String debug_host = p_uri.replace("tcp://", ""); @@ -234,13 +220,30 @@ RemoteDebuggerPeer *RemoteDebuggerPeerTCP::create(const String &p_uri) { debug_host = debug_host.substr(0, sep_pos); } - RemoteDebuggerPeerTCP *peer = memnew(RemoteDebuggerPeerTCP); - Error err = peer->connect_to_host(debug_host, debug_port); - if (err != OK) { - memdelete(peer); - return nullptr; + IPAddress ip; + if (debug_host.is_valid_ip_address()) { + ip = debug_host; + } else { + ip = IP::get_singleton()->resolve_hostname(debug_host); } - return peer; + + Ref stream; + stream.instantiate(); + ERR_FAIL_COND_V_MSG(stream->connect_to_host(ip, debug_port) != OK, nullptr, vformat("Remote Debugger: Unable to connect to host '%s:%d'.", debug_host, debug_port)); + ERR_FAIL_COND_V(_try_connect(stream), nullptr); + return memnew(RemoteDebuggerPeerTCP(stream)); +} + +RemoteDebuggerPeer *RemoteDebuggerPeerTCP::create_unix(const String &p_uri) { + ERR_FAIL_COND_V(!p_uri.begins_with("unix://"), nullptr); + + String debug_path = p_uri.replace("unix://", ""); + Ref stream; + stream.instantiate(); + Error err = stream->connect_to_host(debug_path); + ERR_FAIL_COND_V_MSG(err != OK && err != ERR_BUSY, nullptr, vformat("Remote Debugger: Unable to connect to socket path '%s'.", debug_path)); + ERR_FAIL_COND_V(_try_connect(stream), nullptr); + return memnew(RemoteDebuggerPeerTCP(stream)); } RemoteDebuggerPeer::RemoteDebuggerPeer() { diff --git a/core/debugger/remote_debugger_peer.h b/core/debugger/remote_debugger_peer.h index de7621ba331..6942c09bf96 100644 --- a/core/debugger/remote_debugger_peer.h +++ b/core/debugger/remote_debugger_peer.h @@ -31,6 +31,7 @@ #pragma once #include "core/io/stream_peer_tcp.h" +#include "core/io/stream_peer_uds.h" #include "core/object/ref_counted.h" #include "core/os/mutex.h" #include "core/os/thread.h" @@ -59,7 +60,7 @@ class RemoteDebuggerPeerTCP : public RemoteDebuggerPeer { GDSOFTCLASS(RemoteDebuggerPeerTCP, RemoteDebuggerPeer); private: - Ref tcp_client; + Ref tcp_client; Mutex mutex; Thread thread; List in_queue; @@ -78,11 +79,11 @@ private: void _poll(); void _write_out(); void _read_in(); + static Error _try_connect(Ref p_stream); public: - static RemoteDebuggerPeer *create(const String &p_uri); - - Error connect_to_host(const String &p_host, uint16_t p_port); + static RemoteDebuggerPeer *create_tcp(const String &p_uri); + static RemoteDebuggerPeer *create_unix(const String &p_uri); bool is_peer_connected() override; int get_max_message_size() const override; @@ -92,6 +93,7 @@ public: void poll() override; void close() override; - RemoteDebuggerPeerTCP(Ref p_stream = Ref()); + RemoteDebuggerPeerTCP(Ref p_stream); + RemoteDebuggerPeerTCP(); ~RemoteDebuggerPeerTCP(); }; diff --git a/core/io/net_socket.h b/core/io/net_socket.h index 716693ce979..3ee3d284d11 100644 --- a/core/io/net_socket.h +++ b/core/io/net_socket.h @@ -54,21 +54,61 @@ public: TYPE_UDP, }; - virtual Error open(Type p_type, IP::Type &ip_type) = 0; + enum class Family { + NONE, + INET, + UNIX, + }; + + class Address { + Family _family = Family::NONE; + CharString _path; + IPAddress _ip; + uint16_t _port = 0; + + public: + _FORCE_INLINE_ Family get_family() const { return _family; } + _FORCE_INLINE_ bool is_inet() const { return _family == Family::INET; } + _FORCE_INLINE_ bool is_unix() const { return _family == Family::UNIX; } + _FORCE_INLINE_ bool is_valid() const { return is_inet() || is_unix(); } + + _FORCE_INLINE_ const IPAddress &ip() const { return _ip; } + _FORCE_INLINE_ const uint16_t &port() const { return _port; } + + _FORCE_INLINE_ const CharString &get_path() const { return _path; } + + Address() {} + + Address(const IPAddress &p_addr, uint16_t p_port) : + _family(Family::INET) { + _ip = p_addr; + _port = p_port; + } + + Address(const String &p_path) : + _family(Family::UNIX), _path(p_path.utf8()) { + } + + Address(const CharString &p_path) : + _family(Family::UNIX), _path(p_path) { + } + }; + + virtual Error open(Family p_family, Type p_type, IP::Type &r_ip_type) = 0; virtual void close() = 0; - virtual Error bind(IPAddress p_addr, uint16_t p_port) = 0; + virtual Error bind(Address p_addr) = 0; virtual Error listen(int p_max_pending) = 0; - virtual Error connect_to_host(IPAddress p_addr, uint16_t p_port) = 0; + virtual Error connect_to_host(Address p_addr) = 0; virtual Error poll(PollType p_type, int timeout) const = 0; virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read) = 0; virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek = false) = 0; virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent) = 0; virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) = 0; - virtual Ref accept(IPAddress &r_ip, uint16_t &r_port) = 0; + virtual Ref accept(Address &r_addr) = 0; virtual bool is_open() const = 0; virtual int get_available_bytes() const = 0; - virtual Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) const = 0; + virtual Error get_socket_address(Address *r_addr) const = 0; virtual Error set_broadcasting_enabled(bool p_enabled) = 0; // Returns OK if the socket option has been set successfully. virtual void set_blocking_enabled(bool p_enabled) = 0; diff --git a/core/io/packet_peer_udp.cpp b/core/io/packet_peer_udp.cpp index c662d72f8d0..478c2d76ba8 100644 --- a/core/io/packet_peer_udp.cpp +++ b/core/io/packet_peer_udp.cpp @@ -52,7 +52,7 @@ Error PacketPeerUDP::join_multicast_group(IPAddress p_multi_address, const Strin if (!_sock->is_open()) { IP::Type ip_type = p_multi_address.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6; - Error err = _sock->open(NetSocket::TYPE_UDP, ip_type); + Error err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_UDP, ip_type); ERR_FAIL_COND_V(err != OK, err); _sock->set_blocking_enabled(false); _sock->set_broadcasting_enabled(broadcast); @@ -141,7 +141,7 @@ Error PacketPeerUDP::put_packet(const uint8_t *p_buffer, int p_buffer_size) { if (!_sock->is_open()) { IP::Type ip_type = peer_addr.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6; - err = _sock->open(NetSocket::TYPE_UDP, ip_type); + err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_UDP, ip_type); ERR_FAIL_COND_V(err != OK, err); _sock->set_blocking_enabled(false); _sock->set_broadcasting_enabled(broadcast); @@ -186,7 +186,7 @@ Error PacketPeerUDP::bind(int p_port, const IPAddress &p_bind_address, int p_rec ip_type = p_bind_address.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6; } - err = _sock->open(NetSocket::TYPE_UDP, ip_type); + err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_UDP, ip_type); if (err != OK) { return ERR_CANT_CREATE; @@ -194,7 +194,8 @@ Error PacketPeerUDP::bind(int p_port, const IPAddress &p_bind_address, int p_rec _sock->set_blocking_enabled(false); _sock->set_broadcasting_enabled(broadcast); - err = _sock->bind(p_bind_address, p_port); + NetSocket::Address addr(p_bind_address, p_port); + err = _sock->bind(addr); if (err != OK) { _sock->close(); @@ -231,12 +232,13 @@ Error PacketPeerUDP::connect_to_host(const IPAddress &p_host, int p_port) { if (!_sock->is_open()) { IP::Type ip_type = p_host.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6; - err = _sock->open(NetSocket::TYPE_UDP, ip_type); + err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_UDP, ip_type); ERR_FAIL_COND_V(err != OK, ERR_CANT_OPEN); _sock->set_blocking_enabled(false); } - err = _sock->connect_to_host(p_host, p_port); + NetSocket::Address addr(p_host, p_port); + err = _sock->connect_to_host(addr); // I see no reason why we should get ERR_BUSY (wouldblock/eagain) here. // This is UDP, so connect is only used to tell the OS to which socket @@ -345,9 +347,9 @@ int PacketPeerUDP::get_packet_port() const { } int PacketPeerUDP::get_local_port() const { - uint16_t local_port; - _sock->get_socket_address(nullptr, &local_port); - return local_port; + NetSocket::Address addr; + _sock->get_socket_address(&addr); + return addr.port(); } void PacketPeerUDP::set_dest_address(const IPAddress &p_address, int p_port) { diff --git a/core/io/socket_server.cpp b/core/io/socket_server.cpp new file mode 100644 index 00000000000..6929f675c95 --- /dev/null +++ b/core/io/socket_server.cpp @@ -0,0 +1,90 @@ +/**************************************************************************/ +/* socket_server.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "socket_server.h" + +void SocketServer::_bind_methods() { + ClassDB::bind_method(D_METHOD("is_connection_available"), &SocketServer::is_connection_available); + ClassDB::bind_method(D_METHOD("is_listening"), &SocketServer::is_listening); + ClassDB::bind_method(D_METHOD("stop"), &SocketServer::stop); + ClassDB::bind_method(D_METHOD("take_socket_connection"), &SocketServer::take_socket_connection); +} + +Error SocketServer::_listen(const NetSocket::Address &p_addr) { + DEV_ASSERT(_sock.is_valid()); + DEV_ASSERT(_sock->is_open()); + + _sock->set_blocking_enabled(false); + Error err = _sock->bind(p_addr); + + if (err != OK) { + _sock->close(); + return ERR_ALREADY_IN_USE; + } + + err = _sock->listen(MAX_PENDING_CONNECTIONS); + + if (err != OK) { + _sock->close(); + return FAILED; + } + return OK; +} + +bool SocketServer::is_listening() const { + ERR_FAIL_COND_V(_sock.is_null(), false); + + return _sock->is_open(); +} + +bool SocketServer::is_connection_available() const { + ERR_FAIL_COND_V(_sock.is_null(), false); + + if (!_sock->is_open()) { + return false; + } + + Error err = _sock->poll(NetSocket::POLL_TYPE_IN, 0); + return (err == OK); +} + +void SocketServer::stop() { + if (_sock.is_valid()) { + _sock->close(); + } +} + +SocketServer::SocketServer() : + _sock(NetSocket::create()) { +} + +SocketServer::~SocketServer() { + stop(); +} diff --git a/core/io/socket_server.h b/core/io/socket_server.h new file mode 100644 index 00000000000..f51029c32ea --- /dev/null +++ b/core/io/socket_server.h @@ -0,0 +1,77 @@ +/**************************************************************************/ +/* socket_server.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "core/io/net_socket.h" +#include "core/io/stream_peer_socket.h" + +class SocketServer : public RefCounted { + GDCLASS(SocketServer, RefCounted); + +protected: + enum { + MAX_PENDING_CONNECTIONS = 8, + }; + + Ref _sock; + static void _bind_methods(); + + Error _listen(const NetSocket::Address &p_addr); + + template + Ref _take_connection() { + Ref conn; + if (!is_connection_available()) { + return conn; + } + + Ref ns; + NetSocket::Address addr; + ns = _sock->accept(addr); + if (ns.is_null()) { + return conn; + } + + conn.instantiate(); + conn->accept_socket(ns, addr); + return conn; + } + +public: + bool is_listening() const; + bool is_connection_available() const; + virtual Ref take_socket_connection() = 0; + + void stop(); // Stop listening + + SocketServer(); + ~SocketServer(); +}; diff --git a/core/io/stream_peer_socket.compat.inc b/core/io/stream_peer_socket.compat.inc new file mode 100644 index 00000000000..65bd92f107c --- /dev/null +++ b/core/io/stream_peer_socket.compat.inc @@ -0,0 +1,52 @@ +/**************************************************************************/ +/* stream_peer_socket.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +namespace compat::StreamPeerTCP { +enum class Status { + STATUS_NONE = StreamPeerSocket::STATUS_NONE, + STATUS_CONNECTING = StreamPeerSocket::STATUS_CONNECTING, + STATUS_CONNECTED = StreamPeerSocket::STATUS_CONNECTED, + STATUS_ERROR = StreamPeerSocket::STATUS_ERROR, +}; +} + +VARIANT_ENUM_CAST(compat::StreamPeerTCP::Status); + +compat::StreamPeerTCP::Status StreamPeerSocket::_get_status_compat_107954() const { + return (compat::StreamPeerTCP::Status)get_status(); +} + +void StreamPeerSocket::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("get_status"), &StreamPeerSocket::_get_status_compat_107954); +} + +#endif diff --git a/core/io/stream_peer_socket.cpp b/core/io/stream_peer_socket.cpp new file mode 100644 index 00000000000..a60ea118ed6 --- /dev/null +++ b/core/io/stream_peer_socket.cpp @@ -0,0 +1,236 @@ +/**************************************************************************/ +/* stream_peer_socket.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "stream_peer_socket.h" +#include "stream_peer_socket.compat.inc" + +Error StreamPeerSocket::poll() { + if (status == STATUS_CONNECTED) { + Error err; + err = _sock->poll(NetSocket::POLL_TYPE_IN, 0); + if (err == OK) { + // FIN received + if (_sock->get_available_bytes() == 0) { + disconnect_from_host(); + return OK; + } + } + // Also poll write + err = _sock->poll(NetSocket::POLL_TYPE_IN_OUT, 0); + if (err != OK && err != ERR_BUSY) { + // Got an error + disconnect_from_host(); + status = STATUS_ERROR; + return err; + } + return OK; + } else if (status != STATUS_CONNECTING) { + return OK; + } + + Error err = _sock->connect_to_host(peer_address); + + if (err == OK) { + status = STATUS_CONNECTED; + return OK; + } else if (err == ERR_BUSY) { + // Check for connect timeout + if (OS::get_singleton()->get_ticks_msec() > timeout) { + disconnect_from_host(); + status = STATUS_ERROR; + return ERR_CONNECTION_ERROR; + } + // Still trying to connect + return OK; + } + + disconnect_from_host(); + status = STATUS_ERROR; + return ERR_CONNECTION_ERROR; +} + +Error StreamPeerSocket::write(const uint8_t *p_data, int p_bytes, int &r_sent, bool p_block) { + ERR_FAIL_COND_V(_sock.is_null(), ERR_UNAVAILABLE); + + if (status != STATUS_CONNECTED) { + return FAILED; + } + + Error err; + int data_to_send = p_bytes; + const uint8_t *offset = p_data; + int total_sent = 0; + + while (data_to_send) { + int sent_amount = 0; + err = _sock->send(offset, data_to_send, sent_amount); + + if (err != OK) { + if (err != ERR_BUSY) { + disconnect_from_host(); + return FAILED; + } + + if (!p_block) { + r_sent = total_sent; + return OK; + } + + // Block and wait for the socket to accept more data + err = _sock->poll(NetSocket::POLL_TYPE_OUT, -1); + if (err != OK) { + disconnect_from_host(); + return FAILED; + } + } else { + data_to_send -= sent_amount; + offset += sent_amount; + total_sent += sent_amount; + } + } + + r_sent = total_sent; + + return OK; +} + +Error StreamPeerSocket::read(uint8_t *p_buffer, int p_bytes, int &r_received, bool p_block) { + if (status != STATUS_CONNECTED) { + return FAILED; + } + + Error err; + int to_read = p_bytes; + int total_read = 0; + r_received = 0; + + while (to_read) { + int read = 0; + err = _sock->recv(p_buffer + total_read, to_read, read); + + if (err != OK) { + if (err != ERR_BUSY) { + disconnect_from_host(); + return FAILED; + } + + if (!p_block) { + r_received = total_read; + return OK; + } + + err = _sock->poll(NetSocket::POLL_TYPE_IN, -1); + + if (err != OK) { + disconnect_from_host(); + return FAILED; + } + + } else if (read == 0) { + disconnect_from_host(); + r_received = total_read; + return ERR_FILE_EOF; + + } else { + to_read -= read; + total_read += read; + + if (!p_block) { + r_received = total_read; + return OK; + } + } + } + + r_received = total_read; + + return OK; +} + +StreamPeerSocket::Status StreamPeerSocket::get_status() const { + return status; +} + +void StreamPeerSocket::disconnect_from_host() { + if (_sock.is_valid() && _sock->is_open()) { + _sock->close(); + } + + timeout = 0; + status = STATUS_NONE; + peer_address = NetSocket::Address(); +} + +Error StreamPeerSocket::wait(NetSocket::PollType p_type, int p_timeout) { + ERR_FAIL_COND_V(_sock.is_null() || !_sock->is_open(), ERR_UNAVAILABLE); + return _sock->poll(p_type, p_timeout); +} + +Error StreamPeerSocket::put_data(const uint8_t *p_data, int p_bytes) { + int total; + return write(p_data, p_bytes, total, true); +} + +Error StreamPeerSocket::put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) { + return write(p_data, p_bytes, r_sent, false); +} + +Error StreamPeerSocket::get_data(uint8_t *p_buffer, int p_bytes) { + int total; + return read(p_buffer, p_bytes, total, true); +} + +Error StreamPeerSocket::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) { + return read(p_buffer, p_bytes, r_received, false); +} + +int StreamPeerSocket::get_available_bytes() const { + ERR_FAIL_COND_V(_sock.is_null(), -1); + return _sock->get_available_bytes(); +} + +void StreamPeerSocket::_bind_methods() { + ClassDB::bind_method(D_METHOD("poll"), &StreamPeerSocket::poll); + ClassDB::bind_method(D_METHOD("get_status"), &StreamPeerSocket::get_status); + ClassDB::bind_method(D_METHOD("disconnect_from_host"), &StreamPeerSocket::disconnect_from_host); + + BIND_ENUM_CONSTANT(STATUS_NONE); + BIND_ENUM_CONSTANT(STATUS_CONNECTING); + BIND_ENUM_CONSTANT(STATUS_CONNECTED); + BIND_ENUM_CONSTANT(STATUS_ERROR); +} + +StreamPeerSocket::StreamPeerSocket() : + _sock(NetSocket::create()) { +} + +StreamPeerSocket::~StreamPeerSocket() { + disconnect_from_host(); +} diff --git a/core/io/stream_peer_socket.h b/core/io/stream_peer_socket.h new file mode 100644 index 00000000000..2b3c7d14cd5 --- /dev/null +++ b/core/io/stream_peer_socket.h @@ -0,0 +1,93 @@ +/**************************************************************************/ +/* stream_peer_socket.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "core/io/net_socket.h" +#include "core/io/stream_peer.h" + +#ifndef DISABLE_DEPRECATED +namespace compat::StreamPeerTCP { +enum class Status; +} //namespace compat::StreamPeerTCP +#endif + +class StreamPeerSocket : public StreamPeer { + GDCLASS(StreamPeerSocket, StreamPeer); + +public: + enum Status { + STATUS_NONE, + STATUS_CONNECTING, + STATUS_CONNECTED, + STATUS_ERROR, + }; + +protected: +#ifndef DISABLE_DEPRECATED + compat::StreamPeerTCP::Status _get_status_compat_107954() const; + static void _bind_compatibility_methods(); +#endif + + Ref _sock; + uint64_t timeout = 0; + Status status = STATUS_NONE; + NetSocket::Address peer_address; + + Error write(const uint8_t *p_data, int p_bytes, int &r_sent, bool p_block); + Error read(uint8_t *p_buffer, int p_bytes, int &r_received, bool p_block); + + static void _bind_methods(); + +public: + virtual void accept_socket(Ref p_sock, const NetSocket::Address &p_addr) = 0; + + void disconnect_from_host(); + + int get_available_bytes() const override; + Status get_status() const; + + // Poll socket updating its state. + Error poll(); + + // Wait or check for writable, readable. + Error wait(NetSocket::PollType p_type, int p_timeout = 0); + + // Read/Write from StreamPeer + Error put_data(const uint8_t *p_data, int p_bytes) override; + Error put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) override; + Error get_data(uint8_t *p_buffer, int p_bytes) override; + Error get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) override; + + StreamPeerSocket(); + virtual ~StreamPeerSocket(); +}; + +VARIANT_ENUM_CAST(StreamPeerSocket::Status); diff --git a/core/io/stream_peer_tcp.cpp b/core/io/stream_peer_tcp.cpp index 1ad38cbb46e..d5b5f34cc71 100644 --- a/core/io/stream_peer_tcp.cpp +++ b/core/io/stream_peer_tcp.cpp @@ -32,60 +32,14 @@ #include "core/config/project_settings.h" -Error StreamPeerTCP::poll() { - if (status == STATUS_CONNECTED) { - Error err; - err = _sock->poll(NetSocket::POLL_TYPE_IN, 0); - if (err == OK) { - // FIN received - if (_sock->get_available_bytes() == 0) { - disconnect_from_host(); - return OK; - } - } - // Also poll write - err = _sock->poll(NetSocket::POLL_TYPE_IN_OUT, 0); - if (err != OK && err != ERR_BUSY) { - // Got an error - disconnect_from_host(); - status = STATUS_ERROR; - return err; - } - return OK; - } else if (status != STATUS_CONNECTING) { - return OK; - } - - Error err = _sock->connect_to_host(peer_host, peer_port); - - if (err == OK) { - status = STATUS_CONNECTED; - return OK; - } else if (err == ERR_BUSY) { - // Check for connect timeout - if (OS::get_singleton()->get_ticks_msec() > timeout) { - disconnect_from_host(); - status = STATUS_ERROR; - return ERR_CONNECTION_ERROR; - } - // Still trying to connect - return OK; - } - - disconnect_from_host(); - status = STATUS_ERROR; - return ERR_CONNECTION_ERROR; -} - -void StreamPeerTCP::accept_socket(Ref p_sock, IPAddress p_host, uint16_t p_port) { +void StreamPeerTCP::accept_socket(Ref p_sock, const NetSocket::Address &p_addr) { _sock = p_sock; _sock->set_blocking_enabled(false); timeout = OS::get_singleton()->get_ticks_msec() + (((uint64_t)GLOBAL_GET("network/limits/tcp/connect_timeout_seconds")) * 1000); status = STATUS_CONNECTED; - peer_host = p_host; - peer_port = p_port; + peer_address = p_addr; } Error StreamPeerTCP::bind(int p_port, const IPAddress &p_host) { @@ -97,12 +51,13 @@ Error StreamPeerTCP::bind(int p_port, const IPAddress &p_host) { if (p_host.is_wildcard()) { ip_type = IP::TYPE_ANY; } - Error err = _sock->open(NetSocket::TYPE_TCP, ip_type); + Error err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_TCP, ip_type); if (err != OK) { return err; } _sock->set_blocking_enabled(false); - return _sock->bind(p_host, p_port); + NetSocket::Address addr(p_host, p_port); + return _sock->bind(addr); } Error StreamPeerTCP::connect_to_host(const IPAddress &p_host, int p_port) { @@ -113,7 +68,7 @@ Error StreamPeerTCP::connect_to_host(const IPAddress &p_host, int p_port) { if (!_sock->is_open()) { IP::Type ip_type = p_host.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6; - Error err = _sock->open(NetSocket::TYPE_TCP, ip_type); + Error err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_TCP, ip_type); if (err != OK) { return err; } @@ -121,7 +76,9 @@ Error StreamPeerTCP::connect_to_host(const IPAddress &p_host, int p_port) { } timeout = OS::get_singleton()->get_ticks_msec() + (((uint64_t)GLOBAL_GET("network/limits/tcp/connect_timeout_seconds")) * 1000); - Error err = _sock->connect_to_host(p_host, p_port); + + NetSocket::Address addr(p_host, p_port); + Error err = _sock->connect_to_host(addr); if (err == OK) { status = STATUS_CONNECTED; @@ -133,106 +90,7 @@ Error StreamPeerTCP::connect_to_host(const IPAddress &p_host, int p_port) { return FAILED; } - peer_host = p_host; - peer_port = p_port; - - return OK; -} - -Error StreamPeerTCP::write(const uint8_t *p_data, int p_bytes, int &r_sent, bool p_block) { - ERR_FAIL_COND_V(_sock.is_null(), ERR_UNAVAILABLE); - - if (status != STATUS_CONNECTED) { - return FAILED; - } - - Error err; - int data_to_send = p_bytes; - const uint8_t *offset = p_data; - int total_sent = 0; - - while (data_to_send) { - int sent_amount = 0; - err = _sock->send(offset, data_to_send, sent_amount); - - if (err != OK) { - if (err != ERR_BUSY) { - disconnect_from_host(); - return FAILED; - } - - if (!p_block) { - r_sent = total_sent; - return OK; - } - - // Block and wait for the socket to accept more data - err = _sock->poll(NetSocket::POLL_TYPE_OUT, -1); - if (err != OK) { - disconnect_from_host(); - return FAILED; - } - } else { - data_to_send -= sent_amount; - offset += sent_amount; - total_sent += sent_amount; - } - } - - r_sent = total_sent; - - return OK; -} - -Error StreamPeerTCP::read(uint8_t *p_buffer, int p_bytes, int &r_received, bool p_block) { - if (status != STATUS_CONNECTED) { - return FAILED; - } - - Error err; - int to_read = p_bytes; - int total_read = 0; - r_received = 0; - - while (to_read) { - int read = 0; - err = _sock->recv(p_buffer + total_read, to_read, read); - - if (err != OK) { - if (err != ERR_BUSY) { - disconnect_from_host(); - return FAILED; - } - - if (!p_block) { - r_received = total_read; - return OK; - } - - err = _sock->poll(NetSocket::POLL_TYPE_IN, -1); - - if (err != OK) { - disconnect_from_host(); - return FAILED; - } - - } else if (read == 0) { - disconnect_from_host(); - r_received = total_read; - return ERR_FILE_EOF; - - } else { - to_read -= read; - total_read += read; - - if (!p_block) { - r_received = total_read; - return OK; - } - } - } - - r_received = total_read; + peer_address = addr; return OK; } @@ -242,61 +100,18 @@ void StreamPeerTCP::set_no_delay(bool p_enabled) { _sock->set_tcp_no_delay_enabled(p_enabled); } -StreamPeerTCP::Status StreamPeerTCP::get_status() const { - return status; -} - -void StreamPeerTCP::disconnect_from_host() { - if (_sock.is_valid() && _sock->is_open()) { - _sock->close(); - } - - timeout = 0; - status = STATUS_NONE; - peer_host = IPAddress(); - peer_port = 0; -} - -Error StreamPeerTCP::wait(NetSocket::PollType p_type, int p_timeout) { - ERR_FAIL_COND_V(_sock.is_null() || !_sock->is_open(), ERR_UNAVAILABLE); - return _sock->poll(p_type, p_timeout); -} - -Error StreamPeerTCP::put_data(const uint8_t *p_data, int p_bytes) { - int total; - return write(p_data, p_bytes, total, true); -} - -Error StreamPeerTCP::put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) { - return write(p_data, p_bytes, r_sent, false); -} - -Error StreamPeerTCP::get_data(uint8_t *p_buffer, int p_bytes) { - int total; - return read(p_buffer, p_bytes, total, true); -} - -Error StreamPeerTCP::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) { - return read(p_buffer, p_bytes, r_received, false); -} - -int StreamPeerTCP::get_available_bytes() const { - ERR_FAIL_COND_V(_sock.is_null(), -1); - return _sock->get_available_bytes(); -} - IPAddress StreamPeerTCP::get_connected_host() const { - return peer_host; + return peer_address.ip(); } int StreamPeerTCP::get_connected_port() const { - return peer_port; + return peer_address.port(); } int StreamPeerTCP::get_local_port() const { - uint16_t local_port; - _sock->get_socket_address(nullptr, &local_port); - return local_port; + NetSocket::Address addr; + _sock->get_socket_address(&addr); + return addr.port(); } Error StreamPeerTCP::_connect(const String &p_address, int p_port) { @@ -316,24 +131,8 @@ Error StreamPeerTCP::_connect(const String &p_address, int p_port) { void StreamPeerTCP::_bind_methods() { ClassDB::bind_method(D_METHOD("bind", "port", "host"), &StreamPeerTCP::bind, DEFVAL("*")); ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port"), &StreamPeerTCP::_connect); - ClassDB::bind_method(D_METHOD("poll"), &StreamPeerTCP::poll); - ClassDB::bind_method(D_METHOD("get_status"), &StreamPeerTCP::get_status); ClassDB::bind_method(D_METHOD("get_connected_host"), &StreamPeerTCP::get_connected_host); ClassDB::bind_method(D_METHOD("get_connected_port"), &StreamPeerTCP::get_connected_port); ClassDB::bind_method(D_METHOD("get_local_port"), &StreamPeerTCP::get_local_port); - ClassDB::bind_method(D_METHOD("disconnect_from_host"), &StreamPeerTCP::disconnect_from_host); ClassDB::bind_method(D_METHOD("set_no_delay", "enabled"), &StreamPeerTCP::set_no_delay); - - BIND_ENUM_CONSTANT(STATUS_NONE); - BIND_ENUM_CONSTANT(STATUS_CONNECTING); - BIND_ENUM_CONSTANT(STATUS_CONNECTED); - BIND_ENUM_CONSTANT(STATUS_ERROR); -} - -StreamPeerTCP::StreamPeerTCP() : - _sock(Ref(NetSocket::create())) { -} - -StreamPeerTCP::~StreamPeerTCP() { - disconnect_from_host(); } diff --git a/core/io/stream_peer_tcp.h b/core/io/stream_peer_tcp.h index 80097a663a4..e472a46cf05 100644 --- a/core/io/stream_peer_tcp.h +++ b/core/io/stream_peer_tcp.h @@ -32,62 +32,24 @@ #include "core/io/ip.h" #include "core/io/ip_address.h" -#include "core/io/net_socket.h" -#include "core/io/stream_peer.h" +#include "core/io/stream_peer_socket.h" -class StreamPeerTCP : public StreamPeer { - GDCLASS(StreamPeerTCP, StreamPeer); - -public: - enum Status { - STATUS_NONE, - STATUS_CONNECTING, - STATUS_CONNECTED, - STATUS_ERROR, - }; +class StreamPeerTCP : public StreamPeerSocket { + GDCLASS(StreamPeerTCP, StreamPeerSocket); protected: - Ref _sock; - uint64_t timeout = 0; - Status status = STATUS_NONE; - IPAddress peer_host; - uint16_t peer_port = 0; - Error _connect(const String &p_address, int p_port); - Error write(const uint8_t *p_data, int p_bytes, int &r_sent, bool p_block); - Error read(uint8_t *p_buffer, int p_bytes, int &r_received, bool p_block); static void _bind_methods(); public: - void accept_socket(Ref p_sock, IPAddress p_host, uint16_t p_port); + void accept_socket(Ref p_sock, const NetSocket::Address &p_addr) override; Error bind(int p_port, const IPAddress &p_host); Error connect_to_host(const IPAddress &p_host, int p_port); IPAddress get_connected_host() const; int get_connected_port() const; int get_local_port() const; - void disconnect_from_host(); - - int get_available_bytes() const override; - Status get_status() const; void set_no_delay(bool p_enabled); - - // Poll socket updating its state. - Error poll(); - - // Wait or check for writable, readable. - Error wait(NetSocket::PollType p_type, int p_timeout = 0); - - // Read/Write from StreamPeer - Error put_data(const uint8_t *p_data, int p_bytes) override; - Error put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) override; - Error get_data(uint8_t *p_buffer, int p_bytes) override; - Error get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) override; - - StreamPeerTCP(); - ~StreamPeerTCP(); }; - -VARIANT_ENUM_CAST(StreamPeerTCP::Status); diff --git a/core/io/stream_peer_uds.cpp b/core/io/stream_peer_uds.cpp new file mode 100644 index 00000000000..70e3220ded3 --- /dev/null +++ b/core/io/stream_peer_uds.cpp @@ -0,0 +1,99 @@ +/**************************************************************************/ +/* stream_peer_uds.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "stream_peer_uds.h" + +#include "core/config/project_settings.h" + +void StreamPeerUDS::_bind_methods() { + ClassDB::bind_method(D_METHOD("bind", "path"), &StreamPeerUDS::bind); + ClassDB::bind_method(D_METHOD("connect_to_host", "path"), &StreamPeerUDS::connect_to_host); + ClassDB::bind_method(D_METHOD("get_connected_path"), &StreamPeerUDS::get_connected_path); +} + +void StreamPeerUDS::accept_socket(Ref p_sock, const NetSocket::Address &p_addr) { + _sock = p_sock; + _sock->set_blocking_enabled(false); + + timeout = OS::get_singleton()->get_ticks_msec() + (((uint64_t)GLOBAL_GET("network/limits/unix/connect_timeout_seconds")) * 1000); + status = STATUS_CONNECTED; +} + +Error StreamPeerUDS::bind(const String &p_path) { + ERR_FAIL_COND_V(_sock.is_null(), ERR_UNAVAILABLE); + ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE); + + IP::Type ip_type = IP::TYPE_NONE; + Error err = _sock->open(NetSocket::Family::UNIX, NetSocket::TYPE_NONE, ip_type); + if (err != OK) { + return err; + } + _sock->set_blocking_enabled(false); + NetSocket::Address addr(p_path); + return _sock->bind(addr); +} + +Error StreamPeerUDS::connect_to_host(const String &p_path) { + ERR_FAIL_COND_V(_sock.is_null(), ERR_UNAVAILABLE); + ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER); + + if (!_sock->is_open()) { + IP::Type ip_type = IP::TYPE_NONE; + Error err = _sock->open(NetSocket::Family::UNIX, NetSocket::TYPE_NONE, ip_type); + if (err != OK) { + return err; + } + _sock->set_blocking_enabled(false); + } + + timeout = OS::get_singleton()->get_ticks_msec() + (((uint64_t)GLOBAL_GET("network/limits/unix/connect_timeout_seconds")) * 1000); + NetSocket::Address addr(p_path); + Error err = _sock->connect_to_host(addr); + + if (err == OK) { + status = STATUS_CONNECTED; + } else if (err == ERR_BUSY) { + status = STATUS_CONNECTING; + } else { + ERR_PRINT("Connection to remote host failed!"); + disconnect_from_host(); + return FAILED; + } + + peer_address = addr; + peer_path = p_path; + + return OK; +} + +const String StreamPeerUDS::get_connected_path() const { + return String(peer_address.get_path().get_data()); +} diff --git a/core/io/stream_peer_uds.h b/core/io/stream_peer_uds.h new file mode 100644 index 00000000000..529c78f7167 --- /dev/null +++ b/core/io/stream_peer_uds.h @@ -0,0 +1,48 @@ +/**************************************************************************/ +/* stream_peer_uds.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "core/io/stream_peer_socket.h" + +class StreamPeerUDS : public StreamPeerSocket { + GDCLASS(StreamPeerUDS, StreamPeerSocket); + +protected: + String peer_path; + static void _bind_methods(); + +public: + void accept_socket(Ref p_sock, const NetSocket::Address &p_addr) override; + + Error bind(const String &p_path); + Error connect_to_host(const String &p_path); + const String get_connected_path() const; +}; diff --git a/core/io/tcp_server.cpp b/core/io/tcp_server.cpp index 4d17f59036f..e8728bd946e 100644 --- a/core/io/tcp_server.cpp +++ b/core/io/tcp_server.cpp @@ -32,11 +32,8 @@ void TCPServer::_bind_methods() { ClassDB::bind_method(D_METHOD("listen", "port", "bind_address"), &TCPServer::listen, DEFVAL("*")); - ClassDB::bind_method(D_METHOD("is_connection_available"), &TCPServer::is_connection_available); - ClassDB::bind_method(D_METHOD("is_listening"), &TCPServer::is_listening); ClassDB::bind_method(D_METHOD("get_local_port"), &TCPServer::get_local_port); ClassDB::bind_method(D_METHOD("take_connection"), &TCPServer::take_connection); - ClassDB::bind_method(D_METHOD("stop"), &TCPServer::stop); } Error TCPServer::listen(uint16_t p_port, const IPAddress &p_bind_address) { @@ -52,81 +49,21 @@ Error TCPServer::listen(uint16_t p_port, const IPAddress &p_bind_address) { ip_type = p_bind_address.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6; } - err = _sock->open(NetSocket::TYPE_TCP, ip_type); + err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_TCP, ip_type); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); - _sock->set_blocking_enabled(false); _sock->set_reuse_address_enabled(true); - err = _sock->bind(p_bind_address, p_port); - - if (err != OK) { - _sock->close(); - return ERR_ALREADY_IN_USE; - } - - err = _sock->listen(MAX_PENDING_CONNECTIONS); - - if (err != OK) { - _sock->close(); - return FAILED; - } - return OK; + return _listen(NetSocket::Address(p_bind_address, p_port)); } int TCPServer::get_local_port() const { - uint16_t local_port; - _sock->get_socket_address(nullptr, &local_port); - return local_port; -} - -bool TCPServer::is_listening() const { - ERR_FAIL_COND_V(_sock.is_null(), false); - - return _sock->is_open(); -} - -bool TCPServer::is_connection_available() const { - ERR_FAIL_COND_V(_sock.is_null(), false); - - if (!_sock->is_open()) { - return false; - } - - Error err = _sock->poll(NetSocket::POLL_TYPE_IN, 0); - return (err == OK); + NetSocket::Address addr; + _sock->get_socket_address(&addr); + return addr.port(); } Ref TCPServer::take_connection() { - Ref conn; - if (!is_connection_available()) { - return conn; - } - - Ref ns; - IPAddress ip; - uint16_t port = 0; - ns = _sock->accept(ip, port); - if (ns.is_null()) { - return conn; - } - - conn.instantiate(); - conn->accept_socket(ns, ip, port); - return conn; -} - -void TCPServer::stop() { - if (_sock.is_valid()) { - _sock->close(); - } -} - -TCPServer::TCPServer() : - _sock(Ref(NetSocket::create())) { -} - -TCPServer::~TCPServer() { - stop(); + return _take_connection(); } diff --git a/core/io/tcp_server.h b/core/io/tcp_server.h index 943d4c6f1d2..4e3248a846a 100644 --- a/core/io/tcp_server.h +++ b/core/io/tcp_server.h @@ -31,30 +31,18 @@ #pragma once #include "core/io/ip.h" -#include "core/io/net_socket.h" -#include "core/io/stream_peer.h" +#include "core/io/socket_server.h" #include "core/io/stream_peer_tcp.h" -class TCPServer : public RefCounted { - GDCLASS(TCPServer, RefCounted); +class TCPServer : public SocketServer { + GDCLASS(TCPServer, SocketServer); protected: - enum { - MAX_PENDING_CONNECTIONS = 8 - }; - - Ref _sock; static void _bind_methods(); public: Error listen(uint16_t p_port, const IPAddress &p_bind_address = IPAddress("*")); int get_local_port() const; - bool is_listening() const; - bool is_connection_available() const; Ref take_connection(); - - void stop(); // Stop listening - - TCPServer(); - ~TCPServer(); + Ref take_socket_connection() override { return take_connection(); } }; diff --git a/core/io/udp_server.cpp b/core/io/udp_server.cpp index f2fce3b292b..ada0e09a48f 100644 --- a/core/io/udp_server.cpp +++ b/core/io/udp_server.cpp @@ -99,7 +99,7 @@ Error UDPServer::listen(uint16_t p_port, const IPAddress &p_bind_address) { ip_type = p_bind_address.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6; } - err = _sock->open(NetSocket::TYPE_UDP, ip_type); + err = _sock->open(NetSocket::Family::INET, NetSocket::TYPE_UDP, ip_type); if (err != OK) { return ERR_CANT_CREATE; @@ -107,7 +107,8 @@ Error UDPServer::listen(uint16_t p_port, const IPAddress &p_bind_address) { _sock->set_blocking_enabled(false); _sock->set_reuse_address_enabled(true); - err = _sock->bind(p_bind_address, p_port); + NetSocket::Address addr(p_bind_address, p_port); + err = _sock->bind(addr); if (err != OK) { stop(); @@ -117,9 +118,9 @@ Error UDPServer::listen(uint16_t p_port, const IPAddress &p_bind_address) { } int UDPServer::get_local_port() const { - uint16_t local_port; - _sock->get_socket_address(nullptr, &local_port); - return local_port; + NetSocket::Address addr; + _sock->get_socket_address(&addr); + return addr.port(); } bool UDPServer::is_listening() const { diff --git a/core/io/uds_server.cpp b/core/io/uds_server.cpp new file mode 100644 index 00000000000..e93262a01e4 --- /dev/null +++ b/core/io/uds_server.cpp @@ -0,0 +1,52 @@ +/**************************************************************************/ +/* uds_server.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "uds_server.h" + +void UDSServer::_bind_methods() { + ClassDB::bind_method(D_METHOD("listen", "path"), &UDSServer::listen); + ClassDB::bind_method(D_METHOD("take_connection"), &UDSServer::take_connection); +} + +Error UDSServer::listen(const String &p_path) { + ERR_FAIL_COND_V(_sock.is_null(), ERR_UNAVAILABLE); + ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER); + + IP::Type ip_type = IP::TYPE_NONE; + Error err = _sock->open(NetSocket::Family::UNIX, NetSocket::TYPE_NONE, ip_type); + ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); + + return _listen(p_path); +} + +Ref UDSServer::take_connection() { + return _take_connection(); +} diff --git a/core/io/uds_server.h b/core/io/uds_server.h new file mode 100644 index 00000000000..aa359f2109d --- /dev/null +++ b/core/io/uds_server.h @@ -0,0 +1,46 @@ +/**************************************************************************/ +/* uds_server.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "core/io/socket_server.h" +#include "core/io/stream_peer_uds.h" + +class UDSServer : public SocketServer { + GDCLASS(UDSServer, SocketServer); + +protected: + static void _bind_methods(); + +public: + Error listen(const String &p_path); + Ref take_connection(); + Ref take_socket_connection() override { return take_connection(); } +}; diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 6db6979e1ed..e0e2d6836be 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -63,6 +63,7 @@ #include "core/io/tcp_server.h" #include "core/io/translation_loader_po.h" #include "core/io/udp_server.h" +#include "core/io/uds_server.h" #include "core/io/xml_parser.h" #include "core/math/a_star.h" #include "core/math/a_star_grid_2d.h" @@ -198,12 +199,18 @@ void register_core_types() { GDREGISTER_ABSTRACT_CLASS(IP); GDREGISTER_ABSTRACT_CLASS(StreamPeer); + GDREGISTER_ABSTRACT_CLASS(StreamPeerSocket); + GDREGISTER_ABSTRACT_CLASS(SocketServer); GDREGISTER_CLASS(StreamPeerExtension); GDREGISTER_CLASS(StreamPeerBuffer); GDREGISTER_CLASS(StreamPeerGZIP); GDREGISTER_CLASS(StreamPeerTCP); GDREGISTER_CLASS(TCPServer); + // IPC using UNIX domain sockets. + GDREGISTER_CLASS(StreamPeerUDS); + GDREGISTER_CLASS(UDSServer); + GDREGISTER_ABSTRACT_CLASS(PacketPeer); GDREGISTER_CLASS(PacketPeerExtension); GDREGISTER_CLASS(PacketPeerStream); @@ -322,6 +329,7 @@ void register_core_types() { void register_core_settings() { // Since in register core types, globals may not be present. GLOBAL_DEF(PropertyInfo(Variant::INT, "network/limits/tcp/connect_timeout_seconds", PROPERTY_HINT_RANGE, "1,1800,1"), (30)); + GLOBAL_DEF(PropertyInfo(Variant::INT, "network/limits/unix/connect_timeout_seconds", PROPERTY_HINT_RANGE, "1,1800,1"), (30)); GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "network/limits/packet_peer_stream/max_buffer_po2", PROPERTY_HINT_RANGE, "8,64,1,or_greater"), (16)); GLOBAL_DEF(PropertyInfo(Variant::STRING, "network/tls/certificate_bundle_override", PROPERTY_HINT_FILE, "*.crt"), ""); diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 3517967d680..a4c026bf803 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2377,6 +2377,9 @@ Timeout (in seconds) for connection attempts using TCP. + + Timeout (in seconds) for connection attempts using UNIX domain socket. + Maximum size (in kiB) for the [WebRTCDataChannel] input buffer. diff --git a/doc/classes/SocketServer.xml b/doc/classes/SocketServer.xml new file mode 100644 index 00000000000..2142cee8801 --- /dev/null +++ b/doc/classes/SocketServer.xml @@ -0,0 +1,37 @@ + + + + An abstract class for servers based on sockets. + + + A socket server. + + + + + + + + Returns [code]true[/code] if a connection is available for taking. + + + + + + Returns [code]true[/code] if the server is currently listening for connections. + + + + + + Stops listening. + + + + + + If a connection is available, returns a StreamPeerSocket with the connection. + + + + diff --git a/doc/classes/StreamPeerSocket.xml b/doc/classes/StreamPeerSocket.xml new file mode 100644 index 00000000000..5fb592788d5 --- /dev/null +++ b/doc/classes/StreamPeerSocket.xml @@ -0,0 +1,45 @@ + + + + Abstract base class for interacting with socket streams. + + + StreamPeerSocket is an abstract base class that defines common behavior for socket-based streams. + + + + + + + + Disconnects from host. + + + + + + Returns the status of the connection. + + + + + + Polls the socket, updating its state. See [method get_status]. + + + + + + The initial status of the [StreamPeerSocket]. This is also the status after disconnecting. + + + A status representing a [StreamPeerSocket] that is connecting to a host. + + + A status representing a [StreamPeerSocket] that is connected to a host. + + + A status representing a [StreamPeerSocket] in error state. + + + diff --git a/doc/classes/StreamPeerTCP.xml b/doc/classes/StreamPeerTCP.xml index 2824aae4610..2199d06b2bf 100644 --- a/doc/classes/StreamPeerTCP.xml +++ b/doc/classes/StreamPeerTCP.xml @@ -1,5 +1,5 @@ - + A stream peer that handles TCP connections. @@ -27,12 +27,6 @@ Connects to the specified [code]host:port[/code] pair. A hostname will be resolved if valid. Returns [constant OK] on success. - - - - Disconnects from host. - - @@ -51,18 +45,6 @@ Returns the local port to which this peer is bound. - - - - Returns the status of the connection. - - - - - - Poll the socket, updating its state. See [method get_status]. - - @@ -72,18 +54,4 @@ - - - The initial status of the [StreamPeerTCP]. This is also the status after disconnecting. - - - A status representing a [StreamPeerTCP] that is connecting to a host. - - - A status representing a [StreamPeerTCP] that is connected to a host. - - - A status representing a [StreamPeerTCP] in error state. - - diff --git a/doc/classes/StreamPeerUDS.xml b/doc/classes/StreamPeerUDS.xml new file mode 100644 index 00000000000..cb606341b5a --- /dev/null +++ b/doc/classes/StreamPeerUDS.xml @@ -0,0 +1,35 @@ + + + + A stream peer that handles UNIX Domain Socket (UDS) connections. + + + A stream peer that handles UNIX Domain Socket (UDS) connections. This object can be used to connect to UDS servers, or also is returned by a UDS server. Unix Domain Sockets provide inter-process communication on the same machine using the filesystem namespace. + [b]Note:[/b] UNIX Domain Sockets are only available on UNIX-like systems (Linux, macOS, etc.) and are not supported on Windows. + + + + + + + + + Opens the UDS socket, and binds it to the specified socket path. + This method is generally not needed, and only used to force the subsequent call to [method connect_to_host] to use the specified [param path] as the source address. + + + + + + + Connects to the specified UNIX Domain Socket path. Returns [constant OK] on success. + + + + + + Returns the socket path of this peer. + + + + diff --git a/doc/classes/TCPServer.xml b/doc/classes/TCPServer.xml index 1daf84b5f1b..83c7e85cde8 100644 --- a/doc/classes/TCPServer.xml +++ b/doc/classes/TCPServer.xml @@ -1,5 +1,5 @@ - + A TCP server. @@ -16,18 +16,6 @@ Returns the local port this server is listening to. - - - - Returns [code]true[/code] if a connection is available for taking. - - - - - - Returns [code]true[/code] if the server is currently listening for connections. - - @@ -39,12 +27,6 @@ If [param bind_address] is set to any valid address (e.g. [code]"192.168.1.101"[/code], [code]"::1"[/code], etc.), the server will only listen on the interface with that address (or fail if no interface with the given address exists). - - - - Stops listening. - - diff --git a/doc/classes/UDSServer.xml b/doc/classes/UDSServer.xml new file mode 100644 index 00000000000..f22b38204f1 --- /dev/null +++ b/doc/classes/UDSServer.xml @@ -0,0 +1,28 @@ + + + + A Unix Domain Socket (UDS) server. + + + A Unix Domain Socket (UDS) server. Listens to connections on a socket path and returns a [StreamPeerUDS] when it gets an incoming connection. Unix Domain Sockets provide inter-process communication on the same machine using the filesystem namespace. + [b]Note:[/b] Unix Domain Sockets are only available on Unix-like systems (Linux, macOS, etc.) and are not supported on Windows. + + + + + + + + + Listens on the socket at [param path]. The socket file will be created at the specified path. + [b]Note:[/b] The socket file must not already exist at the specified path. You may need to remove any existing socket file before calling this method. + + + + + + If a connection is available, returns a StreamPeerUDS with the connection. + + + + diff --git a/drivers/unix/net_socket_unix.cpp b/drivers/unix/net_socket_unix.cpp index 87f52bbe025..829d97c49f1 100644 --- a/drivers/unix/net_socket_unix.cpp +++ b/drivers/unix/net_socket_unix.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -94,6 +95,20 @@ size_t NetSocketUnix::_set_addr_storage(struct sockaddr_storage *p_addr, const I } } +socklen_t NetSocketUnix::_unix_set_sockaddr(struct sockaddr_un *p_addr, const CharString &p_path) { + memset(p_addr, 0, sizeof(struct sockaddr_un)); + p_addr->sun_family = AF_UNIX; + + // Path must not exceed maximum path length for Unix domain socket + size_t path_len = p_path.length(); + ERR_FAIL_COND_V(path_len >= sizeof(p_addr->sun_path) - 1, 0); + + // Regular file system socket + memcpy(p_addr->sun_path, p_path.get_data(), path_len); + p_addr->sun_path[path_len] = '\0'; + return sizeof(struct sockaddr_un); +} + void NetSocketUnix::_set_ip_port(struct sockaddr_storage *p_addr, IPAddress *r_ip, uint16_t *r_port) { if (p_addr->ss_family == AF_INET) { struct sockaddr_in *addr4 = (struct sockaddr_in *)p_addr; @@ -172,8 +187,14 @@ bool NetSocketUnix::_can_use_ip(const IPAddress &p_ip, const bool p_for_bind) co return !(_ip_type != IP::TYPE_ANY && !p_ip.is_wildcard() && _ip_type != type); } +bool NetSocketUnix::_can_use_path(const CharString &p_path) const { + // Path must not exceed maximum path length for Unix domain socket + return !p_path.is_empty() && (size_t)p_path.length() < sizeof(((sockaddr_un *)0)->sun_path); +} + _FORCE_INLINE_ Error NetSocketUnix::_change_multicast_group(IPAddress p_ip, String p_if_name, bool p_add) { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(_family != Family::INET, ERR_UNAVAILABLE); ERR_FAIL_COND_V(!_can_use_ip(p_ip, false), ERR_INVALID_PARAMETER); // Need to force level and af_family to IP(v4) when using dual stacking and provided multicast group is IPv4. @@ -240,36 +261,36 @@ void NetSocketUnix::_set_close_exec_enabled(bool p_enabled) { fcntl(_sock, F_SETFD, opts | FD_CLOEXEC); } -Error NetSocketUnix::open(Type p_sock_type, IP::Type &ip_type) { - ERR_FAIL_COND_V(is_open(), ERR_ALREADY_IN_USE); - ERR_FAIL_COND_V(ip_type > IP::TYPE_ANY || ip_type < IP::TYPE_NONE, ERR_INVALID_PARAMETER); +Error NetSocketUnix::_inet_open(Type p_sock_type, IP::Type &r_ip_type) { + ERR_FAIL_COND_V(r_ip_type > IP::TYPE_ANY || r_ip_type < IP::TYPE_NONE, ERR_INVALID_PARAMETER); #if defined(__OpenBSD__) // OpenBSD does not support dual stacking, fallback to IPv4 only. - if (ip_type == IP::TYPE_ANY) { - ip_type = IP::TYPE_IPV4; + if (r_ip_type == IP::TYPE_ANY) { + r_ip_type = IP::TYPE_IPV4; } #endif - int family = ip_type == IP::TYPE_IPV4 ? AF_INET : AF_INET6; + int family = r_ip_type == IP::TYPE_IPV4 ? AF_INET : AF_INET6; int protocol = p_sock_type == TYPE_TCP ? IPPROTO_TCP : IPPROTO_UDP; int type = p_sock_type == TYPE_TCP ? SOCK_STREAM : SOCK_DGRAM; _sock = socket(family, type, protocol); - if (_sock == -1 && ip_type == IP::TYPE_ANY) { + if (_sock == -1 && r_ip_type == IP::TYPE_ANY) { // Careful here, changing the referenced parameter so the caller knows that we are using an IPv4 socket // in place of a dual stack one, and further calls to _set_sock_addr will work as expected. - ip_type = IP::TYPE_IPV4; + r_ip_type = IP::TYPE_IPV4; family = AF_INET; _sock = socket(family, type, protocol); } ERR_FAIL_COND_V(_sock == -1, FAILED); - _ip_type = ip_type; + _ip_type = r_ip_type; + _family = Family::INET; if (family == AF_INET6) { // Select IPv4 over IPv6 mapping. - set_ipv6_only_enabled(ip_type != IP::TYPE_ANY); + set_ipv6_only_enabled(r_ip_type != IP::TYPE_ANY); } if (protocol == IPPROTO_UDP) { @@ -293,18 +314,59 @@ Error NetSocketUnix::open(Type p_sock_type, IP::Type &ip_type) { return OK; } +Error NetSocketUnix::_unix_open() { + _sock = socket(AF_UNIX, SOCK_STREAM, 0); + ERR_FAIL_COND_V(_sock == -1, FAILED); + + _family = Family::UNIX; + + _set_close_exec_enabled(true); + +#if defined(SO_NOSIGPIPE) + // Disable SIGPIPE (should only be relevant to stream sockets, but seems to affect UDP too on iOS). + int par = 1; + if (setsockopt(_sock, SOL_SOCKET, SO_NOSIGPIPE, &par, sizeof(int)) != 0) { + print_verbose("Unable to turn off SIGPIPE on socket."); + } +#endif + + return OK; +} + +Error NetSocketUnix::open(NetSocket::Family p_family, NetSocket::Type p_sock_type, IP::Type &r_ip_type) { + ERR_FAIL_COND_V(is_open(), ERR_ALREADY_IN_USE); + + switch (p_family) { + case Family::INET: + return _inet_open(p_sock_type, r_ip_type); + case Family::UNIX: + return _unix_open(); + case Family::NONE: + default: + return ERR_INVALID_PARAMETER; + } +} + void NetSocketUnix::close() { if (_sock != -1) { ::close(_sock); + + if (_family == Family::UNIX) { + if (_unlink_on_close) { + ::unlink(_unix_path.get_data()); + _unlink_on_close = false; + _unix_path = CharString(); + } + } } _sock = -1; + _family = Family::NONE; _ip_type = IP::TYPE_NONE; _is_stream = false; } -Error NetSocketUnix::bind(IPAddress p_addr, uint16_t p_port) { - ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); +Error NetSocketUnix::_inet_bind(IPAddress p_addr, uint16_t p_port) { ERR_FAIL_COND_V(!_can_use_ip(p_addr, true), ERR_INVALID_PARAMETER); sockaddr_storage addr; @@ -320,6 +382,69 @@ Error NetSocketUnix::bind(IPAddress p_addr, uint16_t p_port) { return OK; } +Error NetSocketUnix::_unix_bind(const CharString &p_path) { + ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER); + + struct sockaddr_un addr; + socklen_t addr_size = _unix_set_sockaddr(&addr, p_path); + ERR_FAIL_COND_V(addr_size == 0, ERR_INVALID_PARAMETER); + + // If the socket file exists, attempt to remove it. + if (access(p_path.get_data(), F_OK) == 0) { + // Check if it's a socket + struct stat st; + if (stat(p_path.get_data(), &st) == 0) { + if (S_ISSOCK(st.st_mode)) { + // It is a socket, try to remove it. + if (unlink(p_path.get_data()) != 0) { + // Failed to remove existing socket file. + return FAILED; + } + } else { + // It's not a socket, don't remove it. + return ERR_ALREADY_EXISTS; + } + } + } + + _unlink_on_close = true; + + if (::bind(_sock, (struct sockaddr *)&addr, addr_size) != 0) { + NetError err = _get_socket_error(); + print_verbose("Failed to bind socket. Error: " + itos(err) + "."); + close(); + switch (err) { + case ERR_NET_UNAUTHORIZED: + return ERR_UNAUTHORIZED; + default: + return ERR_UNAVAILABLE; + } + } + + return OK; +} + +Error NetSocketUnix::bind(NetSocket::Address p_addr) { + ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(_family != p_addr.get_family(), ERR_INVALID_PARAMETER); + switch (p_addr.get_family()) { + case Family::INET: { + Error res = _inet_bind(p_addr.ip(), p_addr.port()); + ERR_FAIL_COND_V(res != OK, res); + } break; + case Family::UNIX: { + _unix_path = p_addr.get_path(); + Error res = _unix_bind(_unix_path); + ERR_FAIL_COND_V(res != OK, res); + } break; + case Family::NONE: + default: + return ERR_INVALID_PARAMETER; + } + + return OK; +} + Error NetSocketUnix::listen(int p_max_pending) { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); @@ -333,8 +458,7 @@ Error NetSocketUnix::listen(int p_max_pending) { return OK; } -Error NetSocketUnix::connect_to_host(IPAddress p_host, uint16_t p_port) { - ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); +Error NetSocketUnix::_inet_connect_to_host(IPAddress p_host, uint16_t p_port) { ERR_FAIL_COND_V(!_can_use_ip(p_host, false), ERR_INVALID_PARAMETER); struct sockaddr_storage addr; @@ -361,6 +485,49 @@ Error NetSocketUnix::connect_to_host(IPAddress p_host, uint16_t p_port) { return OK; } +Error NetSocketUnix::_unix_connect_to_host(const CharString &p_path) { + ERR_FAIL_COND_V(!_can_use_path(p_path), ERR_INVALID_PARAMETER); + + struct sockaddr_un addr; + socklen_t addr_size = _unix_set_sockaddr(&addr, p_path); + ERR_FAIL_COND_V(addr_size == 0, ERR_INVALID_PARAMETER); + + if (::connect(_sock, (struct sockaddr *)&addr, addr_size) != 0) { + NetError err = _get_socket_error(); + switch (err) { + case ERR_NET_ADDRESS_INVALID_OR_UNAVAILABLE: + return ERR_INVALID_PARAMETER; + // Still waiting to connect, try again in a while. + case ERR_NET_WOULD_BLOCK: + case ERR_NET_IN_PROGRESS: + return ERR_BUSY; + case ERR_NET_UNAUTHORIZED: + return ERR_UNAUTHORIZED; + default: + print_verbose("Connection to host failed."); + close(); + return FAILED; + } + } + + return OK; +} + +Error NetSocketUnix::connect_to_host(NetSocket::Address p_addr) { + ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(_family != p_addr.get_family(), ERR_INVALID_PARAMETER); + + switch (p_addr.get_family()) { + case Family::INET: + return _inet_connect_to_host(p_addr.ip(), p_addr.port()); + case Family::UNIX: + return _unix_connect_to_host(p_addr.get_path()); + case Family::NONE: + default: + return ERR_INVALID_PARAMETER; + } +} + Error NetSocketUnix::poll(PollType p_type, int p_timeout) const { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); @@ -418,6 +585,7 @@ Error NetSocketUnix::recv(uint8_t *p_buffer, int p_len, int &r_read) { Error NetSocketUnix::recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek) { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(_family != Family::INET, ERR_UNAVAILABLE); struct sockaddr_storage from; socklen_t len = sizeof(struct sockaddr_storage); @@ -459,7 +627,7 @@ Error NetSocketUnix::send(const uint8_t *p_buffer, int p_len, int &r_sent) { int flags = 0; #ifdef MSG_NOSIGNAL - if (_is_stream) { + if (_is_stream || _family == Family::UNIX) { flags = MSG_NOSIGNAL; } #endif @@ -482,6 +650,7 @@ Error NetSocketUnix::send(const uint8_t *p_buffer, int p_len, int &r_sent) { Error NetSocketUnix::sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) { ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(_family != Family::INET, ERR_UNAVAILABLE); struct sockaddr_storage addr; size_t addr_size = _set_addr_storage(&addr, p_ip, p_port, _ip_type); @@ -580,9 +749,7 @@ int NetSocketUnix::get_available_bytes() const { return len; } -Error NetSocketUnix::get_socket_address(IPAddress *r_ip, uint16_t *r_port) const { - ERR_FAIL_COND_V(!is_open(), FAILED); - +Error NetSocketUnix::_inet_get_socket_address(IPAddress *r_ip, uint16_t *r_port) const { struct sockaddr_storage saddr; socklen_t len = sizeof(saddr); if (getsockname(_sock, (struct sockaddr *)&saddr, &len) != 0) { @@ -594,17 +761,40 @@ Error NetSocketUnix::get_socket_address(IPAddress *r_ip, uint16_t *r_port) const return OK; } -Ref NetSocketUnix::accept(IPAddress &r_ip, uint16_t &r_port) { - Ref out; - ERR_FAIL_COND_V(!is_open(), out); +Error NetSocketUnix::get_socket_address(NetSocket::Address *r_addr) const { + ERR_FAIL_COND_V(!is_open(), FAILED); + switch (_family) { + case Family::INET: { + IPAddress ip; + uint16_t port = 0; + Error res = _inet_get_socket_address(&ip, &port); + ERR_FAIL_COND_V(res != OK, res); + if (r_addr) { + Address addr(ip, port); + *r_addr = addr; + } + } break; + case Family::UNIX: { + if (r_addr) { + *r_addr = Address(_unix_path); + } + } break; + case Family::NONE: + default: + return FAILED; + } + return OK; +} + +Ref NetSocketUnix::_inet_accept(IPAddress &r_ip, uint16_t &r_port) { struct sockaddr_storage their_addr; socklen_t size = sizeof(their_addr); int fd = ::accept(_sock, (struct sockaddr *)&their_addr, &size); if (fd == -1) { _get_socket_error(); print_verbose("Error when accepting socket connection."); - return out; + return Ref(); } _set_ip_port(&their_addr, &r_ip, &r_port); @@ -615,6 +805,48 @@ Ref NetSocketUnix::accept(IPAddress &r_ip, uint16_t &r_port) { return Ref(ns); } +Ref NetSocketUnix::_unix_accept() { + struct sockaddr_un addr; + socklen_t addr_len = sizeof(addr); + + int fd = ::accept(_sock, (struct sockaddr *)&addr, &addr_len); + if (fd == -1) { + _get_socket_error(); + print_verbose("Error when accepting socket connection."); + return Ref(); + } + + NetSocketUnix *ret = memnew(NetSocketUnix); + ret->_sock = fd; + ret->_family = _family; + ret->_unix_path = _unix_path; + ret->set_blocking_enabled(false); + return Ref(ret); +} + +Ref NetSocketUnix::accept(NetSocket::Address &r_addr) { + Ref out; + ERR_FAIL_COND_V(!is_open(), out); + + switch (_family) { + case Family::INET: { + IPAddress ip; + uint16_t port; + out = _inet_accept(ip, port); + if (out.is_valid()) { + r_addr = Address(ip, port); + } + } break; + case Family::UNIX: { + out = _unix_accept(); + } break; + case Family::NONE: + default: + break; + } + return out; +} + Error NetSocketUnix::join_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) { return _change_multicast_group(p_multi_address, p_if_name, true); } diff --git a/drivers/unix/net_socket_unix.h b/drivers/unix/net_socket_unix.h index edd0df08368..3f64420d387 100644 --- a/drivers/unix/net_socket_unix.h +++ b/drivers/unix/net_socket_unix.h @@ -35,14 +35,19 @@ #include "core/io/net_socket.h" #include +#include class NetSocketUnix : public NetSocket { GDSOFTCLASS(NetSocketUnix, NetSocket); private: int _sock = -1; + Family _family = Family::NONE; IP::Type _ip_type = IP::TYPE_NONE; bool _is_stream = false; + CharString _unix_path; + // If this is Family::UNIX, + bool _unlink_on_close = false; enum NetError { ERR_NET_WOULD_BLOCK, @@ -63,6 +68,19 @@ protected: static NetSocket *_create_func(); bool _can_use_ip(const IPAddress &p_ip, const bool p_for_bind) const; + bool _can_use_path(const CharString &p_path) const; + + Error _inet_open(Type p_sock_type, IP::Type &r_ip_type); + Error _inet_bind(IPAddress p_addr, uint16_t p_port); + Error _inet_connect_to_host(IPAddress p_addr, uint16_t p_port); + Error _inet_get_socket_address(IPAddress *r_ip, uint16_t *r_port) const; + Ref _inet_accept(IPAddress &r_ip, uint16_t &r_port); + + static socklen_t _unix_set_sockaddr(struct sockaddr_un *p_addr, const CharString &p_path); + Error _unix_open(); + Error _unix_bind(const CharString &p_path); + Error _unix_connect_to_host(const CharString &p_path); + Ref _unix_accept(); public: static void make_default(); @@ -70,21 +88,21 @@ public: static void _set_ip_port(struct sockaddr_storage *p_addr, IPAddress *r_ip, uint16_t *r_port); static size_t _set_addr_storage(struct sockaddr_storage *p_addr, const IPAddress &p_ip, uint16_t p_port, IP::Type p_ip_type); - virtual Error open(Type p_sock_type, IP::Type &ip_type) override; + virtual Error open(Family p_family, Type p_sock_type, IP::Type &r_ip_type) override; virtual void close() override; - virtual Error bind(IPAddress p_addr, uint16_t p_port) override; + virtual Error bind(Address p_addr) override; virtual Error listen(int p_max_pending) override; - virtual Error connect_to_host(IPAddress p_host, uint16_t p_port) override; + virtual Error connect_to_host(Address p_addr) override; virtual Error poll(PollType p_type, int timeout) const override; virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read) override; virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek = false) override; virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent) override; virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) override; - virtual Ref accept(IPAddress &r_ip, uint16_t &r_port) override; + virtual Ref accept(Address &r_addr) override; virtual bool is_open() const override; virtual int get_available_bytes() const override; - virtual Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) const override; + virtual Error get_socket_address(Address *r_addr) const override; virtual Error set_broadcasting_enabled(bool p_enabled) override; virtual void set_blocking_enabled(bool p_enabled) override; diff --git a/drivers/windows/net_socket_winsock.cpp b/drivers/windows/net_socket_winsock.cpp index 3fe7fc619e9..c5f0edd28b3 100644 --- a/drivers/windows/net_socket_winsock.cpp +++ b/drivers/windows/net_socket_winsock.cpp @@ -217,7 +217,8 @@ void NetSocketWinSock::_set_socket(SOCKET p_sock, IP::Type p_ip_type, bool p_is_ _is_stream = p_is_stream; } -Error NetSocketWinSock::open(Type p_sock_type, IP::Type &ip_type) { +Error NetSocketWinSock::open(Family p_family, Type p_sock_type, IP::Type &ip_type) { + ERR_FAIL_COND_V(p_family != Family::INET, ERR_UNAVAILABLE); ERR_FAIL_COND_V(is_open(), ERR_ALREADY_IN_USE); ERR_FAIL_COND_V(ip_type > IP::TYPE_ANY || ip_type < IP::TYPE_NONE, ERR_INVALID_PARAMETER); @@ -275,12 +276,13 @@ void NetSocketWinSock::close() { _is_stream = false; } -Error NetSocketWinSock::bind(IPAddress p_addr, uint16_t p_port) { +Error NetSocketWinSock::bind(Address p_addr) { + ERR_FAIL_COND_V(!p_addr.is_inet(), ERR_UNAVAILABLE); ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); - ERR_FAIL_COND_V(!_can_use_ip(p_addr, true), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!_can_use_ip(p_addr.ip(), true), ERR_INVALID_PARAMETER); sockaddr_storage addr; - size_t addr_size = _set_addr_storage(&addr, p_addr, p_port, _ip_type); + size_t addr_size = _set_addr_storage(&addr, p_addr.ip(), p_addr.port(), _ip_type); if (::bind(_sock, (struct sockaddr *)&addr, addr_size) != 0) { NetError err = _get_socket_error(); @@ -305,12 +307,13 @@ Error NetSocketWinSock::listen(int p_max_pending) { return OK; } -Error NetSocketWinSock::connect_to_host(IPAddress p_host, uint16_t p_port) { +Error NetSocketWinSock::connect_to_host(Address p_addr) { + ERR_FAIL_COND_V(!p_addr.is_inet(), ERR_UNAVAILABLE); ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED); - ERR_FAIL_COND_V(!_can_use_ip(p_host, false), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!_can_use_ip(p_addr.ip(), false), ERR_INVALID_PARAMETER); struct sockaddr_storage addr; - size_t addr_size = _set_addr_storage(&addr, p_host, p_port, _ip_type); + size_t addr_size = _set_addr_storage(&addr, p_addr.ip(), p_addr.port(), _ip_type); if (::WSAConnect(_sock, (struct sockaddr *)&addr, addr_size, nullptr, nullptr, nullptr, nullptr) != 0) { NetError err = _get_socket_error(); @@ -567,7 +570,7 @@ int NetSocketWinSock::get_available_bytes() const { return len; } -Error NetSocketWinSock::get_socket_address(IPAddress *r_ip, uint16_t *r_port) const { +Error NetSocketWinSock::get_socket_address(Address *r_addr) const { ERR_FAIL_COND_V(!is_open(), FAILED); struct sockaddr_storage saddr; @@ -577,11 +580,16 @@ Error NetSocketWinSock::get_socket_address(IPAddress *r_ip, uint16_t *r_port) co print_verbose("Error when reading local socket address."); return FAILED; } - _set_ip_port(&saddr, r_ip, r_port); + IPAddress ip; + uint16_t port = 0; + _set_ip_port(&saddr, &ip, &port); + if (r_addr) { + *r_addr = Address(ip, port); + } return OK; } -Ref NetSocketWinSock::accept(IPAddress &r_ip, uint16_t &r_port) { +Ref NetSocketWinSock::accept(Address &r_addr) { Ref out; ERR_FAIL_COND_V(!is_open(), out); @@ -594,7 +602,10 @@ Ref NetSocketWinSock::accept(IPAddress &r_ip, uint16_t &r_port) { return out; } - _set_ip_port(&their_addr, &r_ip, &r_port); + IPAddress ip; + uint16_t port = 0; + _set_ip_port(&their_addr, &ip, &port); + r_addr = Address(ip, port); NetSocketWinSock *ns = memnew(NetSocketWinSock); ns->_set_socket(fd, _ip_type, _is_stream); diff --git a/drivers/windows/net_socket_winsock.h b/drivers/windows/net_socket_winsock.h index 43f9375a07b..3162e1ec64e 100644 --- a/drivers/windows/net_socket_winsock.h +++ b/drivers/windows/net_socket_winsock.h @@ -70,21 +70,21 @@ public: static void _set_ip_port(struct sockaddr_storage *p_addr, IPAddress *r_ip, uint16_t *r_port); static size_t _set_addr_storage(struct sockaddr_storage *p_addr, const IPAddress &p_ip, uint16_t p_port, IP::Type p_ip_type); - virtual Error open(Type p_sock_type, IP::Type &ip_type) override; + virtual Error open(Family p_family, Type p_sock_type, IP::Type &ip_type) override; virtual void close() override; - virtual Error bind(IPAddress p_addr, uint16_t p_port) override; + virtual Error bind(Address p_addr) override; virtual Error listen(int p_max_pending) override; - virtual Error connect_to_host(IPAddress p_host, uint16_t p_port) override; + virtual Error connect_to_host(Address p_addr) override; virtual Error poll(PollType p_type, int timeout) const override; virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read) override; virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek = false) override; virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent) override; virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) override; - virtual Ref accept(IPAddress &r_ip, uint16_t &r_port) override; + virtual Ref accept(Address &r_addr) override; virtual bool is_open() const override; virtual int get_available_bytes() const override; - virtual Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) const override; + virtual Error get_socket_address(Address *r_addr) const override; virtual Error set_broadcasting_enabled(bool p_enabled) override; virtual void set_blocking_enabled(bool p_enabled) override; diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index 5c8054f8956..afd0961106f 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -243,8 +243,7 @@ ScriptEditorDebugger *EditorDebuggerNode::get_default_debugger() const { } String EditorDebuggerNode::get_server_uri() const { - ERR_FAIL_COND_V(server.is_null(), ""); - return server->get_uri(); + return server.is_valid() ? server->get_uri() : ""; } void EditorDebuggerNode::set_keep_open(bool p_keep_open) { diff --git a/editor/debugger/editor_debugger_server.cpp b/editor/debugger/editor_debugger_server.cpp index 8f70fd2666a..a38bbd1fa4c 100644 --- a/editor/debugger/editor_debugger_server.cpp +++ b/editor/debugger/editor_debugger_server.cpp @@ -31,30 +31,36 @@ #include "editor_debugger_server.h" #include "core/io/tcp_server.h" +#include "core/io/uds_server.h" #include "core/os/thread.h" #include "editor/editor_log.h" #include "editor/editor_node.h" #include "editor/settings/editor_settings.h" -class EditorDebuggerServerTCP : public EditorDebuggerServer { - GDSOFTCLASS(EditorDebuggerServerTCP, EditorDebuggerServer); +template +class EditorDebuggerServerSocket : public EditorDebuggerServer { + GDSOFTCLASS(EditorDebuggerServerSocket, EditorDebuggerServer); -private: - Ref server; +protected: + Ref server; String endpoint; public: - static EditorDebuggerServer *create(const String &p_protocol); - virtual void poll() override {} virtual String get_uri() const override; - virtual Error start(const String &p_uri) override; virtual void stop() override; virtual bool is_active() const override; virtual bool is_connection_available() const override; virtual Ref take_connection() override; - EditorDebuggerServerTCP(); + EditorDebuggerServerSocket(); +}; + +class EditorDebuggerServerTCP : public EditorDebuggerServerSocket { +public: + static EditorDebuggerServer *create(const String &p_protocol); + + virtual Error start(const String &p_uri) override; }; EditorDebuggerServer *EditorDebuggerServerTCP::create(const String &p_protocol) { @@ -62,11 +68,13 @@ EditorDebuggerServer *EditorDebuggerServerTCP::create(const String &p_protocol) return memnew(EditorDebuggerServerTCP); } -EditorDebuggerServerTCP::EditorDebuggerServerTCP() { +template +EditorDebuggerServerSocket::EditorDebuggerServerSocket() { server.instantiate(); } -String EditorDebuggerServerTCP::get_uri() const { +template +String EditorDebuggerServerSocket::get_uri() const { return endpoint; } @@ -104,29 +112,61 @@ Error EditorDebuggerServerTCP::start(const String &p_uri) { return OK; } -void EditorDebuggerServerTCP::stop() { +template +void EditorDebuggerServerSocket::stop() { server->stop(); } -bool EditorDebuggerServerTCP::is_active() const { +template +bool EditorDebuggerServerSocket::is_active() const { return server->is_listening(); } -bool EditorDebuggerServerTCP::is_connection_available() const { +template +bool EditorDebuggerServerSocket::is_connection_available() const { return server->is_listening() && server->is_connection_available(); } -Ref EditorDebuggerServerTCP::take_connection() { - ERR_FAIL_COND_V(!is_connection_available(), Ref()); - return memnew(RemoteDebuggerPeerTCP(server->take_connection())); +template +Ref EditorDebuggerServerSocket::take_connection() { + const Ref out; + ERR_FAIL_COND_V(!is_connection_available(), out); + Ref stream = server->take_socket_connection(); + ERR_FAIL_COND_V(stream.is_null(), out); + return memnew(RemoteDebuggerPeerTCP(stream)); +} + +class EditorDebuggerServerUDS : public EditorDebuggerServerSocket { +public: + static EditorDebuggerServer *create(const String &p_protocol); + + virtual Error start(const String &p_uri) override; +}; + +EditorDebuggerServer *EditorDebuggerServerUDS::create(const String &p_protocol) { + ERR_FAIL_COND_V(p_protocol != "unix://", nullptr); + return memnew(EditorDebuggerServerUDS); +} + +Error EditorDebuggerServerUDS::start(const String &p_uri) { + String bind_path = p_uri.is_empty() ? String("/tmp/godot_debugger.sock") : p_uri.replace("unix://", ""); + + const Error err = server->listen(bind_path); + if (err != OK) { + EditorNode::get_log()->add_message(vformat("Cannot listen at path %s, remote debugging unavailable.", bind_path), EditorLog::MSG_TYPE_ERROR); + return err; + } + endpoint = "unix://" + bind_path; + return OK; } /// EditorDebuggerServer HashMap EditorDebuggerServer::protocols; EditorDebuggerServer *EditorDebuggerServer::create(const String &p_protocol) { - ERR_FAIL_COND_V(!protocols.has(p_protocol), nullptr); - return protocols[p_protocol](p_protocol); + CreateServerFunc *create_fn = protocols.getptr(p_protocol); + ERR_FAIL_NULL_V(create_fn, nullptr); + return (*create_fn)(p_protocol); } void EditorDebuggerServer::register_protocol_handler(const String &p_protocol, CreateServerFunc p_func) { @@ -136,6 +176,9 @@ void EditorDebuggerServer::register_protocol_handler(const String &p_protocol, C void EditorDebuggerServer::initialize() { register_protocol_handler("tcp://", EditorDebuggerServerTCP::create); +#if defined(UNIX_ENABLED) + register_protocol_handler("unix://", EditorDebuggerServerUDS::create); +#endif } void EditorDebuggerServer::deinitialize() { diff --git a/editor/run/editor_run_bar.cpp b/editor/run/editor_run_bar.cpp index c882b783539..1fad3e51036 100644 --- a/editor/run/editor_run_bar.cpp +++ b/editor/run/editor_run_bar.cpp @@ -314,8 +314,12 @@ void EditorRunBar::_run_scene(const String &p_scene_path, const Vector & if (!EditorNode::get_singleton()->call_build()) { return; } - - EditorDebuggerNode::get_singleton()->start(); + // Use the existing URI, in case it is overridden by the CLI. + String uri = EditorDebuggerNode::get_singleton()->get_server_uri(); + if (uri.is_empty()) { + uri = "tcp://"; + } + EditorDebuggerNode::get_singleton()->start(uri); Error error = editor_run.run(run_filename, write_movie_file, p_run_args); if (error != OK) { EditorDebuggerNode::get_singleton()->stop(); diff --git a/misc/extension_api_validation/4.5-stable.expected b/misc/extension_api_validation/4.5-stable.expected index 7cafd1fa773..5f3167ec67a 100644 --- a/misc/extension_api_validation/4.5-stable.expected +++ b/misc/extension_api_validation/4.5-stable.expected @@ -8,6 +8,18 @@ Add new entries at the end of the file. ## Changes between 4.5-stable and 4.6-stable +GH-107954 +--------- +Validate extension JSON: API was removed: classes/TCPServer/methods/is_connection_available +Validate extension JSON: API was removed: classes/TCPServer/methods/is_listening +Validate extension JSON: API was removed: classes/TCPServer/methods/stop +Validate extension JSON: API was removed: classes/StreamPeerTCP/methods/disconnect_from_host +Validate extension JSON: API was removed: classes/StreamPeerTCP/methods/get_status +Validate extension JSON: API was removed: classes/StreamPeerTCP/methods/poll + +These were moved to the parent classes, and are still available. + + GH-110250 --------- Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Control/methods/grab_focus': arguments diff --git a/platform/web/net_socket_web.h b/platform/web/net_socket_web.h index fa8f643a31b..1f4db836be4 100644 --- a/platform/web/net_socket_web.h +++ b/platform/web/net_socket_web.h @@ -43,21 +43,21 @@ protected: public: static void make_default(); - virtual Error open(Type p_sock_type, IP::Type &ip_type) override { return ERR_UNAVAILABLE; } + virtual Error open(Family p_family, Type p_sock_type, IP::Type &ip_type) override { return ERR_UNAVAILABLE; } virtual void close() override {} - virtual Error bind(IPAddress p_addr, uint16_t p_port) override { return ERR_UNAVAILABLE; } + virtual Error bind(Address p_addr) override { return ERR_UNAVAILABLE; } virtual Error listen(int p_max_pending) override { return ERR_UNAVAILABLE; } - virtual Error connect_to_host(IPAddress p_host, uint16_t p_port) override { return ERR_UNAVAILABLE; } + virtual Error connect_to_host(Address p_addr) override { return ERR_UNAVAILABLE; } virtual Error poll(PollType p_type, int timeout) const override { return ERR_UNAVAILABLE; } virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read) override { return ERR_UNAVAILABLE; } virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek = false) override { return ERR_UNAVAILABLE; } virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent) override { return ERR_UNAVAILABLE; } virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) override { return ERR_UNAVAILABLE; } - virtual Ref accept(IPAddress &r_ip, uint16_t &r_port) override { return Ref(); } + virtual Ref accept(Address &r_addr) override { return Ref(); } virtual bool is_open() const override { return false; } virtual int get_available_bytes() const override { return -1; } - virtual Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) const override { return ERR_UNAVAILABLE; } + virtual Error get_socket_address(Address *r_addr) const override { return ERR_UNAVAILABLE; } virtual Error set_broadcasting_enabled(bool p_enabled) override { return ERR_UNAVAILABLE; } virtual void set_blocking_enabled(bool p_enabled) override {} diff --git a/tests/core/io/test_uds_server.h b/tests/core/io/test_uds_server.h new file mode 100644 index 00000000000..84e6b346a2b --- /dev/null +++ b/tests/core/io/test_uds_server.h @@ -0,0 +1,302 @@ +/**************************************************************************/ +/* test_uds_server.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "core/io/file_access.h" +#include "core/io/stream_peer_uds.h" +#include "core/io/uds_server.h" +#include "tests/test_macros.h" + +#include + +namespace TestUDSServer { + +#ifdef UNIX_ENABLED + +const String SOCKET_PATH = "/tmp/godot_test_uds_socket"; +const uint32_t SLEEP_DURATION = 1000; +const uint64_t MAX_WAIT_USEC = 2000000; + +void wait_for_condition(std::function f_test) { + const uint64_t time = OS::get_singleton()->get_ticks_usec(); + while (!f_test() && (OS::get_singleton()->get_ticks_usec() - time) < MAX_WAIT_USEC) { + OS::get_singleton()->delay_usec(SLEEP_DURATION); + } +} + +void cleanup_socket_file() { + // Remove socket file if it exists + if (FileAccess::exists(SOCKET_PATH)) { + DirAccess::remove_absolute(SOCKET_PATH); + } +} + +Ref create_server(const String &p_path) { + cleanup_socket_file(); + + Ref server; + server.instantiate(); + + REQUIRE_EQ(server->listen(p_path), Error::OK); + REQUIRE(server->is_listening()); + CHECK_FALSE(server->is_connection_available()); + + return server; +} + +Ref create_client(const String &p_path) { + Ref client; + client.instantiate(); + + Error err = client->connect_to_host(p_path); + REQUIRE_EQ(err, Error::OK); + + // UDS connections may be immediately connected or in connecting state + StreamPeerUDS::Status status = client->get_status(); + REQUIRE((status == StreamPeerUDS::STATUS_CONNECTED || status == StreamPeerUDS::STATUS_CONNECTING)); + + if (status == StreamPeerUDS::STATUS_CONNECTED) { + CHECK_EQ(client->get_connected_path(), p_path); + } + + return client; +} + +Ref accept_connection(Ref &p_server) { + wait_for_condition([&]() { + return p_server->is_connection_available(); + }); + + REQUIRE(p_server->is_connection_available()); + Ref client_from_server = p_server->take_connection(); + REQUIRE(client_from_server.is_valid()); + CHECK_EQ(client_from_server->get_status(), StreamPeerUDS::STATUS_CONNECTED); + + return client_from_server; +} + +TEST_CASE("[UDSServer] Instantiation") { + Ref server; + server.instantiate(); + + REQUIRE(server.is_valid()); + CHECK_FALSE(server->is_listening()); +} + +TEST_CASE("[UDSServer] Accept a connection and receive/send data") { + Ref server = create_server(SOCKET_PATH); + Ref client = create_client(SOCKET_PATH); + Ref client_from_server = accept_connection(server); + + wait_for_condition([&]() { + return client->poll() != Error::OK || client->get_status() == StreamPeerUDS::STATUS_CONNECTED; + }); + + CHECK_EQ(client->get_status(), StreamPeerUDS::STATUS_CONNECTED); + + // Sending data from client to server. + const String hello_world = "Hello World!"; + client->put_string(hello_world); + CHECK_EQ(client_from_server->get_string(), hello_world); + + // Sending data from server to client. + const float pi = 3.1415; + client_from_server->put_float(pi); + CHECK_EQ(client->get_float(), pi); + + client->disconnect_from_host(); + server->stop(); + CHECK_FALSE(server->is_listening()); + + cleanup_socket_file(); +} + +TEST_CASE("[UDSServer] Handle multiple clients at the same time") { + Ref server = create_server(SOCKET_PATH); + + Vector> clients; + for (int i = 0; i < 5; i++) { + clients.push_back(create_client(SOCKET_PATH)); + } + + Vector> clients_from_server; + for (int i = 0; i < clients.size(); i++) { + clients_from_server.push_back(accept_connection(server)); + } + + wait_for_condition([&]() { + bool should_exit = true; + for (Ref &c : clients) { + if (c->poll() != Error::OK) { + return true; + } + StreamPeerUDS::Status status = c->get_status(); + if (status != StreamPeerUDS::STATUS_CONNECTED && status != StreamPeerUDS::STATUS_CONNECTING) { + return true; + } + if (status != StreamPeerUDS::STATUS_CONNECTED) { + should_exit = false; + } + } + return should_exit; + }); + + for (Ref &c : clients) { + REQUIRE_EQ(c->get_status(), StreamPeerUDS::STATUS_CONNECTED); + } + + // Sending data from each client to server. + for (int i = 0; i < clients.size(); i++) { + String hello_client = "Hello " + itos(i); + clients[i]->put_string(hello_client); + CHECK_EQ(clients_from_server[i]->get_string(), hello_client); + } + + for (Ref &c : clients) { + c->disconnect_from_host(); + } + server->stop(); + + cleanup_socket_file(); +} + +TEST_CASE("[UDSServer] When stopped shouldn't accept new connections") { + Ref server = create_server(SOCKET_PATH); + Ref client = create_client(SOCKET_PATH); + Ref client_from_server = accept_connection(server); + + wait_for_condition([&]() { + return client->poll() != Error::OK || client->get_status() == StreamPeerUDS::STATUS_CONNECTED; + }); + + CHECK_EQ(client->get_status(), StreamPeerUDS::STATUS_CONNECTED); + + // Sending data from client to server. + const String hello_world = "Hello World!"; + client->put_string(hello_world); + CHECK_EQ(client_from_server->get_string(), hello_world); + + client->disconnect_from_host(); + server->stop(); + CHECK_FALSE(server->is_listening()); + + // Clean up the socket file after server stops + cleanup_socket_file(); + + // Try to connect to non-existent socket + Ref new_client; + new_client.instantiate(); + Error err = new_client->connect_to_host(SOCKET_PATH); + + // Connection should fail since socket doesn't exist + CHECK_NE(err, Error::OK); + CHECK_FALSE(server->is_connection_available()); + + cleanup_socket_file(); +} + +TEST_CASE("[UDSServer] Should disconnect client") { + Ref server = create_server(SOCKET_PATH); + Ref client = create_client(SOCKET_PATH); + Ref client_from_server = accept_connection(server); + + wait_for_condition([&]() { + return client->poll() != Error::OK || client->get_status() == StreamPeerUDS::STATUS_CONNECTED; + }); + + CHECK_EQ(client->get_status(), StreamPeerUDS::STATUS_CONNECTED); + + // Sending data from client to server. + const String hello_world = "Hello World!"; + client->put_string(hello_world); + CHECK_EQ(client_from_server->get_string(), hello_world); + + client_from_server->disconnect_from_host(); + server->stop(); + CHECK_FALSE(server->is_listening()); + + // Wait for disconnection + wait_for_condition([&]() { + return client->poll() != Error::OK || client->get_status() == StreamPeerUDS::STATUS_NONE; + }); + + // Wait for disconnection + wait_for_condition([&]() { + return client_from_server->poll() != Error::OK || client_from_server->get_status() == StreamPeerUDS::STATUS_NONE; + }); + + CHECK_EQ(client->get_status(), StreamPeerUDS::STATUS_NONE); + CHECK_EQ(client_from_server->get_status(), StreamPeerUDS::STATUS_NONE); + + ERR_PRINT_OFF; + CHECK_EQ(client->get_string(), String()); + CHECK_EQ(client_from_server->get_string(), String()); + ERR_PRINT_ON; + + cleanup_socket_file(); +} + +TEST_CASE("[UDSServer] Test with different socket paths") { + // Test with a different socket path + const String alt_socket_path = "/tmp/godot_test_uds_socket_alt"; + + // Clean up before test + if (FileAccess::exists(alt_socket_path)) { + DirAccess::remove_absolute(alt_socket_path); + } + + Ref server = create_server(alt_socket_path); + Ref client = create_client(alt_socket_path); + Ref client_from_server = accept_connection(server); + + wait_for_condition([&]() { + return client->poll() != Error::OK || client->get_status() == StreamPeerUDS::STATUS_CONNECTED; + }); + + CHECK_EQ(client->get_status(), StreamPeerUDS::STATUS_CONNECTED); + + // Test data exchange + const int test_number = 42; + client->put_32(test_number); + CHECK_EQ(client_from_server->get_32(), test_number); + + client->disconnect_from_host(); + server->stop(); + + // Clean up + if (FileAccess::exists(alt_socket_path)) { + DirAccess::remove_absolute(alt_socket_path); + } +} + +#endif + +} // namespace TestUDSServer diff --git a/tests/test_main.cpp b/tests/test_main.cpp index cee61de273b..322a714fd3b 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -62,6 +62,7 @@ #include "tests/core/io/test_stream_peer_gzip.h" #include "tests/core/io/test_tcp_server.h" #include "tests/core/io/test_udp_server.h" +#include "tests/core/io/test_uds_server.h" #include "tests/core/io/test_xml_parser.h" #include "tests/core/math/test_aabb.h" #include "tests/core/math/test_astar.h" diff --git a/thirdparty/enet/enet_godot.cpp b/thirdparty/enet/enet_godot.cpp index 6465779528a..f1fd07750b3 100644 --- a/thirdparty/enet/enet_godot.cpp +++ b/thirdparty/enet/enet_godot.cpp @@ -74,7 +74,7 @@ public: ENetUDP() { sock = Ref(NetSocket::create()); IP::Type ip_type = IP::TYPE_ANY; - sock->open(NetSocket::TYPE_UDP, ip_type); + sock->open(NetSocket::Family::INET, NetSocket::TYPE_UDP, ip_type); } ~ENetUDP() { @@ -88,11 +88,15 @@ public: Error bind(IPAddress p_ip, uint16_t p_port) { local_address = p_ip; bound = true; - return sock->bind(p_ip, p_port); + NetSocket::Address addr(p_ip, p_port); + return sock->bind(addr); } Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) { - Error err = sock->get_socket_address(r_ip, r_port); + NetSocket::Address addr; + Error err = sock->get_socket_address(&addr); + *r_ip = addr.ip(); + *r_port = addr.port(); if (bound) { *r_ip = local_address; }