From 2c8340bee9ec91b1f2101093fe53b1cb059469f8 Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Thu, 22 Aug 2019 19:03:35 +0200 Subject: [PATCH] UDPServer and PacketPeerUDP connect_to_host. UDP sockets can be "connected" to filter packets from a specific source. In case of a bound socket (e.g. server), a new socket can be created on the same address/port that will receive all packets that are not filtered by a more specific socket (e.g. the previously connect socket). This way, a UDPServer can listen to new packets, and return a new PacketPeerUDP when receiving one, knowing that is a "new client". --- core/io/packet_peer_udp.cpp | 75 +++++++++++++++++++++- core/io/packet_peer_udp.h | 6 ++ core/io/udp_server.cpp | 119 +++++++++++++++++++++++++++++++++++ core/io/udp_server.h | 58 +++++++++++++++++ core/register_core_types.cpp | 2 + 5 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 core/io/udp_server.cpp create mode 100644 core/io/udp_server.h diff --git a/core/io/packet_peer_udp.cpp b/core/io/packet_peer_udp.cpp index 5c98c4fcabb..2d05e5bf645 100644 --- a/core/io/packet_peer_udp.cpp +++ b/core/io/packet_peer_udp.cpp @@ -133,7 +133,11 @@ Error PacketPeerUDP::put_packet(const uint8_t *p_buffer, int p_buffer_size) { } do { - err = _sock->sendto(p_buffer, p_buffer_size, sent, peer_addr, peer_port); + if (connected) { + err = _sock->send(p_buffer, p_buffer_size, sent); + } else { + err = _sock->sendto(p_buffer, p_buffer_size, sent, peer_addr, peer_port); + } if (err != OK) { if (err != ERR_BUSY) return FAILED; @@ -184,12 +188,69 @@ Error PacketPeerUDP::listen(int p_port, const IP_Address &p_bind_address, int p_ return OK; } +Error PacketPeerUDP::connect_socket(Ref p_sock) { + Error err; + int read = 0; + uint16_t r_port; + IP_Address r_ip; + + err = p_sock->recvfrom(recv_buffer, sizeof(recv_buffer), read, r_ip, r_port, true); + ERR_FAIL_COND_V(err != OK, err); + err = p_sock->connect_to_host(r_ip, r_port); + ERR_FAIL_COND_V(err != OK, err); + _sock = p_sock; + peer_addr = r_ip; + peer_port = r_port; + packet_ip = peer_addr; + packet_port = peer_port; + connected = true; + return OK; +} + +Error PacketPeerUDP::connect_to_host(const IP_Address &p_host, int p_port) { + ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE); + ERR_FAIL_COND_V(!p_host.is_valid(), ERR_INVALID_PARAMETER); + + Error err; + + 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_FAIL_COND_V(err != OK, ERR_CANT_OPEN); + _sock->set_blocking_enabled(false); + } + + err = _sock->connect_to_host(p_host, p_port); + + // 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 + // it shuold deliver packets when multiple are bound on the same address/port. + if (err != OK) { + close(); + ERR_FAIL_V_MSG(FAILED, "Unable to connect"); + } + + connected = true; + + peer_addr = p_host; + peer_port = p_port; + + // Flush any packet we might still have in queue. + rb.clear(); + return OK; +} + +bool PacketPeerUDP::is_connected_to_host() const { + return connected; +} + void PacketPeerUDP::close() { if (_sock.is_valid()) _sock->close(); rb.resize(16); queue_count = 0; + connected = false; } Error PacketPeerUDP::wait() { @@ -212,7 +273,13 @@ Error PacketPeerUDP::_poll() { uint16_t port; while (true) { - err = _sock->recvfrom(recv_buffer, sizeof(recv_buffer), read, ip, port); + if (connected) { + err = _sock->recv(recv_buffer, sizeof(recv_buffer), read); + ip = peer_addr; + port = peer_port; + } else { + err = _sock->recvfrom(recv_buffer, sizeof(recv_buffer), read, ip, port); + } if (err != OK) { if (err == ERR_BUSY) @@ -254,6 +321,7 @@ int PacketPeerUDP::get_packet_port() const { void PacketPeerUDP::set_dest_address(const IP_Address &p_address, int p_port) { + ERR_FAIL_COND_MSG(connected, "Destination address cannot be set for connected sockets"); peer_addr = p_address; peer_port = p_port; } @@ -264,6 +332,8 @@ void PacketPeerUDP::_bind_methods() { ClassDB::bind_method(D_METHOD("close"), &PacketPeerUDP::close); ClassDB::bind_method(D_METHOD("wait"), &PacketPeerUDP::wait); ClassDB::bind_method(D_METHOD("is_listening"), &PacketPeerUDP::is_listening); + ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port"), &PacketPeerUDP::connect_to_host); + ClassDB::bind_method(D_METHOD("is_connected_to_host"), &PacketPeerUDP::is_connected_to_host); ClassDB::bind_method(D_METHOD("get_packet_ip"), &PacketPeerUDP::_get_packet_ip); ClassDB::bind_method(D_METHOD("get_packet_port"), &PacketPeerUDP::get_packet_port); ClassDB::bind_method(D_METHOD("set_dest_address", "host", "port"), &PacketPeerUDP::_set_dest_address); @@ -276,6 +346,7 @@ PacketPeerUDP::PacketPeerUDP() : packet_port(0), queue_count(0), peer_port(0), + connected(false), blocking(true), broadcast(false), _sock(Ref(NetSocket::create())) { diff --git a/core/io/packet_peer_udp.h b/core/io/packet_peer_udp.h index 15b4d00c374..b5a9fc9ec3c 100644 --- a/core/io/packet_peer_udp.h +++ b/core/io/packet_peer_udp.h @@ -52,6 +52,7 @@ protected: IP_Address peer_addr; int peer_port; + bool connected; bool blocking; bool broadcast; Ref _sock; @@ -70,6 +71,11 @@ public: void close(); Error wait(); bool is_listening() const; + + Error connect_socket(Ref p_sock); // Used by UDPServer + Error connect_to_host(const IP_Address &p_host, int p_port); + bool is_connected_to_host() const; + IP_Address get_packet_address() const; int get_packet_port() const; void set_dest_address(const IP_Address &p_address, int p_port); diff --git a/core/io/udp_server.cpp b/core/io/udp_server.cpp new file mode 100644 index 00000000000..16b7863cddb --- /dev/null +++ b/core/io/udp_server.cpp @@ -0,0 +1,119 @@ +/*************************************************************************/ +/* udp_server.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "udp_server.h" + +void UDPServer::_bind_methods() { + + ClassDB::bind_method(D_METHOD("listen", "port", "bind_address"), &UDPServer::listen, DEFVAL("*")); + ClassDB::bind_method(D_METHOD("is_connection_available"), &UDPServer::is_connection_available); + ClassDB::bind_method(D_METHOD("is_listening"), &UDPServer::is_listening); + ClassDB::bind_method(D_METHOD("take_connection"), &UDPServer::take_connection); + ClassDB::bind_method(D_METHOD("stop"), &UDPServer::stop); +} + +Error UDPServer::listen(uint16_t p_port, const IP_Address &p_bind_address) { + + ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE); + ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(!p_bind_address.is_valid() && !p_bind_address.is_wildcard(), ERR_INVALID_PARAMETER); + + Error err; + IP::Type ip_type = IP::TYPE_ANY; + + if (p_bind_address.is_valid()) + ip_type = p_bind_address.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6; + + err = _sock->open(NetSocket::TYPE_UDP, ip_type); + + if (err != OK) + return 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) { + stop(); + return err; + } + bind_address = p_bind_address; + bind_port = p_port; + return OK; +} + +bool UDPServer::is_listening() const { + ERR_FAIL_COND_V(!_sock.is_valid(), false); + + return _sock->is_open(); +} + +bool UDPServer::is_connection_available() const { + + ERR_FAIL_COND_V(!_sock.is_valid(), false); + + if (!_sock->is_open()) + return false; + + Error err = _sock->poll(NetSocket::POLL_TYPE_IN, 0); + return (err == OK); +} + +Ref UDPServer::take_connection() { + + Ref conn; + if (!is_connection_available()) { + return conn; + } + + conn = Ref(memnew(PacketPeerUDP)); + conn->connect_socket(_sock); + _sock = Ref(NetSocket::create()); + listen(bind_port, bind_address); + return conn; +} + +void UDPServer::stop() { + + if (_sock.is_valid()) { + _sock->close(); + } + bind_port = 0; + bind_address = IP_Address(); +} + +UDPServer::UDPServer() : + _sock(Ref(NetSocket::create())) { +} + +UDPServer::~UDPServer() { + + stop(); +} diff --git a/core/io/udp_server.h b/core/io/udp_server.h new file mode 100644 index 00000000000..90bb82b62b6 --- /dev/null +++ b/core/io/udp_server.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* udp_server.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 UDP_SERVER_H +#define UDP_SERVER_H + +#include "core/io/net_socket.h" +#include "core/io/packet_peer_udp.h" + +class UDPServer : public Reference { + GDCLASS(UDPServer, Reference); + +protected: + static void _bind_methods(); + int bind_port; + IP_Address bind_address; + Ref _sock; + +public: + Error listen(uint16_t p_port, const IP_Address &p_bind_address = IP_Address("*")); + bool is_listening() const; + bool is_connection_available() const; + Ref take_connection(); + + void stop(); + + UDPServer(); + ~UDPServer(); +}; + +#endif // UDP_SERVER_H diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 91d92895638..fb608361f50 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -53,6 +53,7 @@ #include "core/io/stream_peer_ssl.h" #include "core/io/tcp_server.h" #include "core/io/translation_loader_po.h" +#include "core/io/udp_server.h" #include "core/io/xml_parser.h" #include "core/math/a_star.h" #include "core/math/expression.h" @@ -155,6 +156,7 @@ void register_core_types() { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); // Crypto ClassDB::register_class();