Merge pull request #35091 from Faless/dtls/enet

[3.2] DTLS support + optional ENet encryption
This commit is contained in:
Rémi Verschelde 2020-05-05 13:40:59 +02:00 committed by GitHub
commit ef715f37d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 1804 additions and 75 deletions

54
core/io/dtls_server.cpp Normal file
View file

@ -0,0 +1,54 @@
/*************************************************************************/
/* dtls_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 "dtls_server.h"
#include "core/os/file_access.h"
#include "core/project_settings.h"
DTLSServer *(*DTLSServer::_create)() = NULL;
bool DTLSServer::available = false;
DTLSServer *DTLSServer::create() {
return _create();
}
bool DTLSServer::is_available() {
return available;
}
void DTLSServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("setup", "key", "certificate", "chain"), &DTLSServer::setup, DEFVAL(Ref<X509Certificate>()));
ClassDB::bind_method(D_METHOD("take_connection", "udp_peer"), &DTLSServer::take_connection);
}
DTLSServer::DTLSServer() {
}

57
core/io/dtls_server.h Normal file
View file

@ -0,0 +1,57 @@
/*************************************************************************/
/* dtls_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 DTLS_SERVER_H
#define DTLS_SERVER_H
#include "core/io/net_socket.h"
#include "core/io/packet_peer_dtls.h"
class DTLSServer : public Reference {
GDCLASS(DTLSServer, Reference);
protected:
static DTLSServer *(*_create)();
static void _bind_methods();
static bool available;
public:
static bool is_available();
static DTLSServer *create();
virtual Error setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>()) = 0;
virtual void stop() = 0;
virtual Ref<PacketPeerDTLS> take_connection(Ref<PacketPeerUDP> p_peer) = 0;
DTLSServer();
};
#endif // DTLS_SERVER_H

View file

@ -61,7 +61,7 @@ public:
virtual Error connect_to_host(IP_Address p_addr, uint16_t p_port) = 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, IP_Address &r_ip, uint16_t &r_port) = 0;
virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IP_Address &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, IP_Address p_ip, uint16_t p_port) = 0;
virtual Ref<NetSocket> accept(IP_Address &r_ip, uint16_t &r_port) = 0;

View file

@ -0,0 +1,62 @@
/*************************************************************************/
/* packet_peer_dtls.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 "packet_peer_dtls.h"
#include "core/os/file_access.h"
#include "core/project_settings.h"
PacketPeerDTLS *(*PacketPeerDTLS::_create)() = NULL;
bool PacketPeerDTLS::available = false;
PacketPeerDTLS *PacketPeerDTLS::create() {
return _create();
}
bool PacketPeerDTLS::is_available() {
return available;
}
void PacketPeerDTLS::_bind_methods() {
ClassDB::bind_method(D_METHOD("poll"), &PacketPeerDTLS::poll);
ClassDB::bind_method(D_METHOD("connect_to_peer", "packet_peer", "validate_certs", "for_hostname", "valid_certificate"), &PacketPeerDTLS::connect_to_peer, DEFVAL(true), DEFVAL(String()), DEFVAL(Ref<X509Certificate>()));
ClassDB::bind_method(D_METHOD("get_status"), &PacketPeerDTLS::get_status);
ClassDB::bind_method(D_METHOD("disconnect_from_peer"), &PacketPeerDTLS::disconnect_from_peer);
BIND_ENUM_CONSTANT(STATUS_DISCONNECTED);
BIND_ENUM_CONSTANT(STATUS_HANDSHAKING);
BIND_ENUM_CONSTANT(STATUS_CONNECTED);
BIND_ENUM_CONSTANT(STATUS_ERROR);
BIND_ENUM_CONSTANT(STATUS_ERROR_HOSTNAME_MISMATCH);
}
PacketPeerDTLS::PacketPeerDTLS() {
}

View file

@ -0,0 +1,68 @@
/*************************************************************************/
/* packet_peer_dtls.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 PACKET_PEER_DTLS_H
#define PACKET_PEER_DTLS_H
#include "core/crypto/crypto.h"
#include "core/io/packet_peer_udp.h"
class PacketPeerDTLS : public PacketPeer {
GDCLASS(PacketPeerDTLS, PacketPeer);
protected:
static PacketPeerDTLS *(*_create)();
static void _bind_methods();
static bool available;
public:
enum Status {
STATUS_DISCONNECTED,
STATUS_HANDSHAKING,
STATUS_CONNECTED,
STATUS_ERROR,
STATUS_ERROR_HOSTNAME_MISMATCH
};
virtual void poll() = 0;
virtual Error connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_validate_certs = true, const String &p_for_hostname = String(), Ref<X509Certificate> p_ca_certs = Ref<X509Certificate>()) = 0;
virtual void disconnect_from_peer() = 0;
virtual Status get_status() const = 0;
static PacketPeerDTLS *create();
static bool is_available();
PacketPeerDTLS();
};
VARIANT_ENUM_CAST(PacketPeerDTLS::Status);
#endif // PACKET_PEER_DTLS_H

View file

@ -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<NetSocket> 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>(NetSocket::create())) {

View file

@ -52,6 +52,7 @@ protected:
IP_Address peer_addr;
int peer_port;
bool connected;
bool blocking;
bool broadcast;
Ref<NetSocket> _sock;
@ -70,6 +71,11 @@ public:
void close();
Error wait();
bool is_listening() const;
Error connect_socket(Ref<NetSocket> 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);

119
core/io/udp_server.cpp Normal file
View file

@ -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<PacketPeerUDP> UDPServer::take_connection() {
Ref<PacketPeerUDP> conn;
if (!is_connection_available()) {
return conn;
}
conn = Ref<PacketPeerUDP>(memnew(PacketPeerUDP));
conn->connect_socket(_sock);
_sock = Ref<NetSocket>(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>(NetSocket::create())) {
}
UDPServer::~UDPServer() {
stop();
}

58
core/io/udp_server.h Normal file
View file

@ -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<NetSocket> _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<PacketPeerUDP> take_connection();
void stop();
UDPServer();
~UDPServer();
};
#endif // UDP_SERVER_H

View file

@ -40,12 +40,14 @@
#include "core/func_ref.h"
#include "core/input_map.h"
#include "core/io/config_file.h"
#include "core/io/dtls_server.h"
#include "core/io/http_client.h"
#include "core/io/image_loader.h"
#include "core/io/marshalls.h"
#include "core/io/multiplayer_api.h"
#include "core/io/networked_multiplayer_peer.h"
#include "core/io/packet_peer.h"
#include "core/io/packet_peer_dtls.h"
#include "core/io/packet_peer_udp.h"
#include "core/io/pck_packer.h"
#include "core/io/resource_format_binary.h"
@ -53,6 +55,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 +158,9 @@ void register_core_types() {
ClassDB::register_class<StreamPeerTCP>();
ClassDB::register_class<TCP_Server>();
ClassDB::register_class<PacketPeerUDP>();
ClassDB::register_class<UDPServer>();
ClassDB::register_custom_instance_class<PacketPeerDTLS>();
ClassDB::register_custom_instance_class<DTLSServer>();
// Crypto
ClassDB::register_class<HashingContext>();

View file

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="DTLSServer" inherits="Reference" version="3.2">
<brief_description>
Helper class to implement a DTLS server.
</brief_description>
<description>
This class is used to store the state of a DTLS server. Upon [method setup] it converts connected [PacketPeerUDP] to [PacketPeerDTLS] accepting them via [method take_connection] as DTLS clients. Under the hood, this class is used to store the DTLS state and cookies of the server. The reason of why the state and cookies are needed is outside of the scope of this documentation.
Below a small example of how to use it:
[codeblock]
# server.gd
extends Node
var dtls := DTLSServer.new()
var server := UDPServer.new()
var peers = []
func _ready():
server.listen(4242)
var key = load("key.key") # Your private key.
var cert = load("cert.crt") # Your X509 certificate.
dtls.setup(key, cert)
func _process(delta):
while server.is_connection_available():
var peer : PacketPeerUDP = server.take_connection()
var dtls_peer : PacketPeerDTLS = dtls.take_connection(peer)
if dtls_peer.get_status() != PacketPeerDTLS.STATUS_HANDSHAKING:
continue # It is normal that 50% of the connections fails due to cookie exchange.
print("Peer connected!")
peers.append(dtls_peer)
for p in peers:
p.poll() # Must poll to update the state.
if p.get_status() == PacketPeerDTLS.STATUS_CONNECTED:
while p.get_available_packet_count() &gt; 0:
print("Received message from client: %s" % p.get_packet().get_string_from_utf8())
p.put_packet("Hello DTLS client".to_utf8())
[/codeblock]
[codeblock]
# client.gd
extends Node
var dtls := PacketPeerDTLS.new()
var udp := PacketPeerUDP.new()
var connected = false
func _ready():
udp.connect_to_host("127.0.0.1", 4242)
dtls.connect_to_peer(udp, false) # Use true in production for certificate validation!
func _process(delta):
dtls.poll()
if dtls.get_status() == PacketPeerDTLS.STATUS_CONNECTED:
if !connected:
# Try to contact server
dtls.put_packet("The answer is... 42!".to_utf8())
while dtls.get_available_packet_count() &gt; 0:
print("Connected: %s" % dtls.get_packet().get_string_from_utf8())
connected = true
[/codeblock]
</description>
<tutorials>
</tutorials>
<methods>
<method name="setup">
<return type="int" enum="Error">
</return>
<argument index="0" name="key" type="CryptoKey">
</argument>
<argument index="1" name="certificate" type="X509Certificate">
</argument>
<argument index="2" name="chain" type="X509Certificate" default="null">
</argument>
<description>
Setup the DTLS server to use the given [code]private_key[/code] and provide the given [code]certificate[/code] to clients. You can pass the optional [code]chain[/code] parameter to provide additional CA chain information along with the certificate.
</description>
</method>
<method name="take_connection">
<return type="PacketPeerDTLS">
</return>
<argument index="0" name="udp_peer" type="PacketPeerUDP">
</argument>
<description>
Try to initiate the DTLS handshake with the given [code]udp_peer[/code] which must be already connected (see [method PacketPeerUDP.connect_to_host]).
[b]Note[/b]: You must check that the state of the return PacketPeerUDP is [constant PacketPeerDTLS.STATUS_HANDSHAKING], as it is normal that 50% of the new connections will be invalid due to cookie exchange.
</description>
</method>
</methods>
<constants>
</constants>
</class>

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="PacketPeerDTLS" inherits="PacketPeer" version="3.2">
<brief_description>
DTLS packet peer.
</brief_description>
<description>
This class represents a DTLS peer connection. It can be used to connect to a DTLS server, and is returned by [method DTLSServer.take_connection].
</description>
<tutorials>
</tutorials>
<methods>
<method name="connect_to_peer">
<return type="int" enum="Error">
</return>
<argument index="0" name="packet_peer" type="PacketPeerUDP">
</argument>
<argument index="1" name="validate_certs" type="bool" default="true">
</argument>
<argument index="2" name="for_hostname" type="String" default="&quot;&quot;">
</argument>
<argument index="3" name="valid_certificate" type="X509Certificate" default="null">
</argument>
<description>
Connects a [code]peer[/code] beginning the DTLS handshake using the underlying [PacketPeerUDP] which must be connected (see [method PacketPeerUDP.connect_to_host]). If [code]validate_certs[/code] is [code]true[/code], [PacketPeerDTLS] will validate that the certificate presented by the remote peer and match it with the [code]for_hostname[/code] argument. You can specify a custom [X509Certificate] to use for validation via the [code]valid_certificate[/code] argument.
</description>
</method>
<method name="disconnect_from_peer">
<return type="void">
</return>
<description>
Disconnects this peer, terminating the DTLS session.
</description>
</method>
<method name="get_status" qualifiers="const">
<return type="int" enum="PacketPeerDTLS.Status">
</return>
<description>
Returns the status of the connection. See [enum Status] for values.
</description>
</method>
<method name="poll">
<return type="void">
</return>
<description>
Poll the connection to check for incoming packets. Call this frequently to update the status and keep the connection working.
</description>
</method>
</methods>
<constants>
<constant name="STATUS_DISCONNECTED" value="0" enum="Status">
A status representing a [PacketPeerDTLS] that is disconnected.
</constant>
<constant name="STATUS_HANDSHAKING" value="1" enum="Status">
A status representing a [PacketPeerDTLS] that is currently performing the handshake with a remote peer.
</constant>
<constant name="STATUS_CONNECTED" value="2" enum="Status">
A status representing a [PacketPeerDTLS] that is connected to a remote peer.
</constant>
<constant name="STATUS_ERROR" value="3" enum="Status">
A status representing a [PacketPeerDTLS] in a generic error state.
</constant>
<constant name="STATUS_ERROR_HOSTNAME_MISMATCH" value="4" enum="Status">
An error status that shows a mismatch in the DTLS certificate domain presented by the host and the domain requested for validation.
</constant>
</constants>
</class>

View file

@ -16,6 +16,18 @@
Closes the UDP socket the [PacketPeerUDP] is currently listening on.
</description>
</method>
<method name="connect_to_host">
<return type="int" enum="Error">
</return>
<argument index="0" name="host" type="String">
</argument>
<argument index="1" name="port" type="int">
</argument>
<description>
Calling this method connects this UDP peer to the given [code]host[/code]/[code]port[/code] pair. UDP is in reality connectionless, so this option only means that incoming packets from different addresses are automatically discarded, and that outgoing packets are always sent to the connected address (future calls to [method set_dest_address] are not allowed). This method does not send any data to the remote peer, to do that, use [method PacketPeer.put_var] or [method PacketPeer.put_packet] as usual. See also [UDPServer].
Note: Connecting to the remote peer does not help to protect from malicious attacks like IP spoofing, etc. Think about using an encryption technique like SSL or DTLS if you feel like your application is transfering sensitive information.
</description>
</method>
<method name="get_packet_ip" qualifiers="const">
<return type="String">
</return>
@ -30,6 +42,13 @@
Returns the port of the remote peer that sent the last packet(that was received with [method PacketPeer.get_packet] or [method PacketPeer.get_var]).
</description>
</method>
<method name="is_connected_to_host" qualifiers="const">
<return type="bool">
</return>
<description>
Returns [code]true[/code] if the UDP socket is open and has been connected to a remote address. See [method connect_to_host].
</description>
</method>
<method name="is_listening" qualifiers="const">
<return type="bool">
</return>

98
doc/classes/UDPServer.xml Normal file
View file

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="UDPServer" inherits="Reference" version="3.2">
<brief_description>
Helper class to implement a UDP server.
</brief_description>
<description>
A simple server that opens a UDP socket and returns connected [PacketPeerUDP] upon receiving new packets. See also [method PacketPeerUDP.connect_to_host].
Below a small example of how it can be used:
[codeblock]
# server.gd
extends Node
var server := UDPServer.new()
var peers = []
func _ready():
server.listen(4242)
func _process(delta):
if server.is_connection_available():
var peer : PacketPeerUDP = server.take_connection()
var pkt = peer.get_packet()
print("Accepted peer: %s:%s" % [peer.get_packet_ip(), peer.get_packet_port()])
print("Received data: %s" % [pkt.get_string_from_utf8()])
# Reply so it knows we received the message.
peer.put_packet(pkt)
# Keep a reference so we can keep contacting the remote peer.
peers.append(peer)
for i in range(0, peers.size()):
pass # Do something with the connected peers.
[/codeblock]
[codeblock]
# client.gd
extends Node
var udp := PacketPeerUDP.new()
var connected = false
func _ready():
udp.connect_to_host("127.0.0.1", 4242)
func _process(delta):
if !connected:
# Try to contact server
udp.put_packet("The answer is... 42!".to_utf8())
if udp.get_available_packet_count() &gt; 0:
print("Connected: %s" % udp.get_packet().get_string_from_utf8())
connected = true
[/codeblock]
</description>
<tutorials>
</tutorials>
<methods>
<method name="is_connection_available" qualifiers="const">
<return type="bool">
</return>
<description>
Returns [code]true[/code] if a packet with a new address/port combination is received on the socket.
</description>
</method>
<method name="is_listening" qualifiers="const">
<return type="bool">
</return>
<description>
Returns [code]true[/code] if the socket is open and listening on a port.
</description>
</method>
<method name="listen">
<return type="int" enum="Error">
</return>
<argument index="0" name="port" type="int">
</argument>
<argument index="1" name="bind_address" type="String" default="&quot;*&quot;">
</argument>
<description>
Starts the server by opening a UDP socket listening on the given port. You can optionally specify a [code]bind_address[/code] to only listen for packets sent to that address. See also [method PacketPeerUDP.listen].
</description>
</method>
<method name="stop">
<return type="void">
</return>
<description>
Stops the server, closing the UDP socket if open. Will not disconnect any connected [PacketPeerUDP].
</description>
</method>
<method name="take_connection">
<return type="PacketPeerUDP">
</return>
<description>
Returns a [PacketPeerUDP] connected to the address/port combination of the first packet in queue. Will return [code]null[/code] if no packet is in queue. See also [method PacketPeerUDP.connect_to_host].
</description>
</method>
</methods>
<constants>
</constants>
</class>

View file

@ -544,14 +544,14 @@ Error NetSocketPosix::recv(uint8_t *p_buffer, int p_len, int &r_read) {
return OK;
}
Error NetSocketPosix::recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IP_Address &r_ip, uint16_t &r_port) {
Error NetSocketPosix::recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IP_Address &r_ip, uint16_t &r_port, bool p_peek) {
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
struct sockaddr_storage from;
socklen_t len = sizeof(struct sockaddr_storage);
memset(&from, 0, len);
r_read = ::recvfrom(_sock, SOCK_BUF(p_buffer), p_len, 0, (struct sockaddr *)&from, &len);
r_read = ::recvfrom(_sock, SOCK_BUF(p_buffer), p_len, p_peek ? MSG_PEEK : 0, (struct sockaddr *)&from, &len);
if (r_read < 0) {
NetError err = _get_socket_error();

View file

@ -81,7 +81,7 @@ public:
virtual Error connect_to_host(IP_Address p_host, uint16_t p_port);
virtual Error poll(PollType p_type, int timeout) const;
virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read);
virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IP_Address &r_ip, uint16_t &r_port);
virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IP_Address &r_ip, uint16_t &r_port, bool p_peek = false);
virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent);
virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IP_Address p_ip, uint16_t p_port);
virtual Ref<NetSocket> accept(IP_Address &r_ip, uint16_t &r_port);

View file

@ -104,6 +104,24 @@
The IP used when creating a server. This is set to the wildcard [code]"*"[/code] by default, which binds to all available interfaces. The given IP needs to be in IPv4 or IPv6 address format, for example: [code]"192.168.1.1"[/code].
</description>
</method>
<method name="set_dtls_certificate">
<return type="void">
</return>
<argument index="0" name="certificate" type="X509Certificate">
</argument>
<description>
Configure the [X509Certificate] to use when [member use_dtls] is [code]true[/code]. For servers, you must also setup the [CryptoKey] via [method set_dtls_key].
</description>
</method>
<method name="set_dtls_key">
<return type="void">
</return>
<argument index="0" name="key" type="CryptoKey">
</argument>
<description>
Configure the [CryptoKey] to use when [member use_dtls] is [code]true[/code]. Remember to also call [method set_dtls_certificate] to setup your [X509Certificate].
</description>
</method>
</methods>
<members>
<member name="always_ordered" type="bool" setter="set_always_ordered" getter="is_always_ordered" default="false">
@ -115,6 +133,9 @@
<member name="compression_mode" type="int" setter="set_compression_mode" getter="get_compression_mode" enum="NetworkedMultiplayerENet.CompressionMode" default="0">
The compression method used for network packets. These have different tradeoffs of compression speed versus bandwidth, you may need to test which one works best for your use case if you use compression at all.
</member>
<member name="dtls_verify" type="bool" setter="set_dtls_verify_enabled" getter="is_dtls_verify_enabled" default="true">
Enable or disable certiticate verification when [member use_dtls] [code]true[/code].
</member>
<member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" override="true" default="false" />
<member name="server_relay" type="bool" setter="set_server_relay_enabled" getter="is_server_relay_enabled" default="true">
Enable or disable the server feature that notifies clients of other peers' connection/disconnection, and relays messages between them. When this option is [code]false[/code], clients won't be automatically notified of other peers and won't be able to send them packets through the server.
@ -123,6 +144,10 @@
Set the default channel to be used to transfer data. By default, this value is [code]-1[/code] which means that ENet will only use 2 channels, one for reliable and one for unreliable packets. Channel [code]0[/code] is reserved, and cannot be used. Setting this member to any value between [code]0[/code] and [member channel_count] (excluded) will force ENet to use that channel for sending data.
</member>
<member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="NetworkedMultiplayerPeer.TransferMode" default="2" />
<member name="use_dtls" type="bool" setter="set_dtls_enabled" getter="is_dtls_enabled" default="false">
When enabled, the client or server created by this peer, will use [PacketPeerDTLS] instead of raw UDP sockets for communicating with the remote peer. This will make the communication encrypted with DTLS at the cost of higher resource usage and potentially larger packet size.
Note: When creating a DTLS server, make sure you setup the key/certificate pair via [method set_dtls_key] and [method set_dtls_certificate]. For DTLS clients, have a look at the [member dtls_verify] option, and configure the certificate accordingly via [method set_dtls_certificate].
</member>
</members>
<constants>
<constant name="COMPRESS_NONE" value="0" enum="CompressionMode">

View file

@ -78,6 +78,7 @@ Error NetworkedMultiplayerENet::create_server(int p_port, int p_max_clients, int
ERR_FAIL_COND_V_MSG(p_max_clients < 1 || p_max_clients > 4095, ERR_INVALID_PARAMETER, "The number of clients must be set between 1 and 4095 (inclusive).");
ERR_FAIL_COND_V_MSG(p_in_bandwidth < 0, ERR_INVALID_PARAMETER, "The incoming bandwidth limit must be greater than or equal to 0 (0 disables the limit).");
ERR_FAIL_COND_V_MSG(p_out_bandwidth < 0, ERR_INVALID_PARAMETER, "The outgoing bandwidth limit must be greater than or equal to 0 (0 disables the limit).");
ERR_FAIL_COND_V(dtls_enabled && (dtls_key.is_null() || dtls_cert.is_null()), ERR_INVALID_PARAMETER);
ENetAddress address;
memset(&address, 0, sizeof(address));
@ -105,6 +106,11 @@ Error NetworkedMultiplayerENet::create_server(int p_port, int p_max_clients, int
p_out_bandwidth /* limit outgoing bandwidth if > 0 */);
ERR_FAIL_COND_V_MSG(!host, ERR_CANT_CREATE, "Couldn't create an ENet multiplayer server.");
#ifdef GODOT_ENET
if (dtls_enabled) {
enet_host_dtls_server_setup(host, dtls_key.ptr(), dtls_cert.ptr());
}
#endif
_setup_compressor();
active = true;
@ -156,6 +162,11 @@ Error NetworkedMultiplayerENet::create_client(const String &p_address, int p_por
}
ERR_FAIL_COND_V_MSG(!host, ERR_CANT_CREATE, "Couldn't create the ENet client host.");
#ifdef GODOT_ENET
if (dtls_enabled) {
enet_host_dtls_client_setup(host, dtls_cert.ptr(), dtls_verify, p_address.utf8().get_data());
}
#endif
_setup_compressor();
@ -856,6 +867,12 @@ void NetworkedMultiplayerENet::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_compression_mode", "mode"), &NetworkedMultiplayerENet::set_compression_mode);
ClassDB::bind_method(D_METHOD("get_compression_mode"), &NetworkedMultiplayerENet::get_compression_mode);
ClassDB::bind_method(D_METHOD("set_bind_ip", "ip"), &NetworkedMultiplayerENet::set_bind_ip);
ClassDB::bind_method(D_METHOD("set_dtls_enabled", "enabled"), &NetworkedMultiplayerENet::set_dtls_enabled);
ClassDB::bind_method(D_METHOD("is_dtls_enabled"), &NetworkedMultiplayerENet::is_dtls_enabled);
ClassDB::bind_method(D_METHOD("set_dtls_key", "key"), &NetworkedMultiplayerENet::set_dtls_key);
ClassDB::bind_method(D_METHOD("set_dtls_certificate", "certificate"), &NetworkedMultiplayerENet::set_dtls_certificate);
ClassDB::bind_method(D_METHOD("set_dtls_verify_enabled", "enabled"), &NetworkedMultiplayerENet::set_dtls_verify_enabled);
ClassDB::bind_method(D_METHOD("is_dtls_verify_enabled"), &NetworkedMultiplayerENet::is_dtls_verify_enabled);
ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &NetworkedMultiplayerENet::get_peer_address);
ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &NetworkedMultiplayerENet::get_peer_port);
@ -875,6 +892,8 @@ void NetworkedMultiplayerENet::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "channel_count"), "set_channel_count", "get_channel_count");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "always_ordered"), "set_always_ordered", "is_always_ordered");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "server_relay"), "set_server_relay_enabled", "is_server_relay_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dtls_verify"), "set_dtls_verify_enabled", "is_dtls_verify_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_dtls"), "set_dtls_enabled", "is_dtls_enabled");
BIND_ENUM_CONSTANT(COMPRESS_NONE);
BIND_ENUM_CONSTANT(COMPRESS_RANGE_CODER);
@ -904,6 +923,9 @@ NetworkedMultiplayerENet::NetworkedMultiplayerENet() {
enet_compressor.destroy = enet_compressor_destroy;
bind_ip = IP_Address("*");
dtls_enabled = false;
dtls_verify = true;
}
NetworkedMultiplayerENet::~NetworkedMultiplayerENet() {
@ -920,3 +942,31 @@ void NetworkedMultiplayerENet::set_bind_ip(const IP_Address &p_ip) {
bind_ip = p_ip;
}
void NetworkedMultiplayerENet::set_dtls_enabled(bool p_enabled) {
ERR_FAIL_COND(active);
dtls_enabled = p_enabled;
}
bool NetworkedMultiplayerENet::is_dtls_enabled() const {
return dtls_enabled;
}
void NetworkedMultiplayerENet::set_dtls_verify_enabled(bool p_enabled) {
ERR_FAIL_COND(active);
dtls_verify = p_enabled;
}
bool NetworkedMultiplayerENet::is_dtls_verify_enabled() const {
return dtls_verify;
}
void NetworkedMultiplayerENet::set_dtls_key(Ref<CryptoKey> p_key) {
ERR_FAIL_COND(active);
dtls_key = p_key;
}
void NetworkedMultiplayerENet::set_dtls_certificate(Ref<X509Certificate> p_cert) {
ERR_FAIL_COND(active);
dtls_cert = p_cert;
}

View file

@ -31,6 +31,7 @@
#ifndef NETWORKED_MULTIPLAYER_ENET_H
#define NETWORKED_MULTIPLAYER_ENET_H
#include "core/crypto/crypto.h"
#include "core/io/compression.h"
#include "core/io/networked_multiplayer_peer.h"
@ -111,6 +112,11 @@ private:
IP_Address bind_ip;
bool dtls_enabled;
Ref<CryptoKey> dtls_key;
Ref<X509Certificate> dtls_cert;
bool dtls_verify;
protected:
static void _bind_methods();
@ -166,6 +172,12 @@ public:
~NetworkedMultiplayerENet();
void set_bind_ip(const IP_Address &p_ip);
void set_dtls_enabled(bool p_enabled);
bool is_dtls_enabled() const;
void set_dtls_verify_enabled(bool p_enabled);
bool is_dtls_verify_enabled() const;
void set_dtls_key(Ref<CryptoKey> p_key);
void set_dtls_certificate(Ref<X509Certificate> p_cert);
};
VARIANT_ENUM_CAST(NetworkedMultiplayerENet::CompressionMode);

View file

@ -0,0 +1,78 @@
/*************************************************************************/
/* dtls_server_mbedtls.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 "dtls_server_mbedtls.h"
#include "packet_peer_mbed_dtls.h"
Error DTLSServerMbedTLS::setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain) {
ERR_FAIL_COND_V(_cookies->setup() != OK, ERR_ALREADY_IN_USE);
_key = p_key;
_cert = p_cert;
_ca_chain = p_ca_chain;
return OK;
}
void DTLSServerMbedTLS::stop() {
_cookies->clear();
}
Ref<PacketPeerDTLS> DTLSServerMbedTLS::take_connection(Ref<PacketPeerUDP> p_udp_peer) {
Ref<PacketPeerMbedDTLS> out;
out.instance();
ERR_FAIL_COND_V(!out.is_valid(), out);
ERR_FAIL_COND_V(!p_udp_peer.is_valid(), out);
out->accept_peer(p_udp_peer, _key, _cert, _ca_chain, _cookies);
return out;
}
DTLSServer *DTLSServerMbedTLS::_create_func() {
return memnew(DTLSServerMbedTLS);
}
void DTLSServerMbedTLS::initialize() {
_create = _create_func;
available = true;
}
void DTLSServerMbedTLS::finalize() {
_create = NULL;
available = false;
}
DTLSServerMbedTLS::DTLSServerMbedTLS() {
_cookies.instance();
}
DTLSServerMbedTLS::~DTLSServerMbedTLS() {
stop();
}

View file

@ -0,0 +1,58 @@
/*************************************************************************/
/* dtls_server_mbedtls.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 MBED_DTLS_SERVER_H
#define MBED_DTLS_SERVER_H
#include "core/io/dtls_server.h"
#include "ssl_context_mbedtls.h"
class DTLSServerMbedTLS : public DTLSServer {
private:
static DTLSServer *_create_func();
Ref<CryptoKey> _key;
Ref<X509Certificate> _cert;
Ref<X509Certificate> _ca_chain;
Ref<CookieContextMbedTLS> _cookies;
public:
static void initialize();
static void finalize();
virtual Error setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>());
virtual void stop();
virtual Ref<PacketPeerDTLS> take_connection(Ref<PacketPeerUDP> p_peer);
DTLSServerMbedTLS();
~DTLSServerMbedTLS();
};
#endif // MBED_DTLS_SERVER_H

View file

@ -0,0 +1,297 @@
/*************************************************************************/
/* packet_peer_mbed_dtls.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 "packet_peer_mbed_dtls.h"
#include "mbedtls/platform_util.h"
#include "core/io/stream_peer_ssl.h"
#include "core/os/file_access.h"
int PacketPeerMbedDTLS::bio_send(void *ctx, const unsigned char *buf, size_t len) {
if (buf == NULL || len <= 0) return 0;
PacketPeerMbedDTLS *sp = (PacketPeerMbedDTLS *)ctx;
ERR_FAIL_COND_V(sp == NULL, 0);
Error err = sp->base->put_packet((const uint8_t *)buf, len);
if (err == ERR_BUSY) {
return MBEDTLS_ERR_SSL_WANT_WRITE;
} else if (err != OK) {
ERR_FAIL_V(MBEDTLS_ERR_SSL_INTERNAL_ERROR);
}
return len;
}
int PacketPeerMbedDTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) {
if (buf == NULL || len <= 0) return 0;
PacketPeerMbedDTLS *sp = (PacketPeerMbedDTLS *)ctx;
ERR_FAIL_COND_V(sp == NULL, 0);
int pc = sp->base->get_available_packet_count();
if (pc == 0) {
return MBEDTLS_ERR_SSL_WANT_READ;
} else if (pc < 0) {
ERR_FAIL_V(MBEDTLS_ERR_SSL_INTERNAL_ERROR);
}
const uint8_t *buffer;
int buffer_size = 0;
Error err = sp->base->get_packet(&buffer, buffer_size);
if (err != OK) {
return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
}
copymem(buf, buffer, buffer_size);
return buffer_size;
}
void PacketPeerMbedDTLS::_cleanup() {
ssl_ctx->clear();
base = Ref<PacketPeer>();
status = STATUS_DISCONNECTED;
}
int PacketPeerMbedDTLS::_set_cookie() {
// Setup DTLS session cookie for this client
uint8_t client_id[18];
IP_Address addr = base->get_packet_address();
uint16_t port = base->get_packet_port();
copymem(client_id, addr.get_ipv6(), 16);
copymem(&client_id[16], (uint8_t *)&port, 2);
return mbedtls_ssl_set_client_transport_id(ssl_ctx->get_context(), client_id, 18);
}
Error PacketPeerMbedDTLS::_do_handshake() {
int ret = 0;
while ((ret = mbedtls_ssl_handshake(ssl_ctx->get_context())) != 0) {
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
if (ret != MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED) {
ERR_PRINT("TLS handshake error: " + itos(ret));
SSLContextMbedTLS::print_mbedtls_error(ret);
}
_cleanup();
status = STATUS_ERROR;
return FAILED;
}
// Will retry via poll later
return OK;
}
status = STATUS_CONNECTED;
return OK;
}
Error PacketPeerMbedDTLS::connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_validate_certs, const String &p_for_hostname, Ref<X509Certificate> p_ca_certs) {
ERR_FAIL_COND_V(!p_base.is_valid() || !p_base->is_connected_to_host(), ERR_INVALID_PARAMETER);
base = p_base;
int ret = 0;
int authmode = p_validate_certs ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE;
Error err = ssl_ctx->init_client(MBEDTLS_SSL_TRANSPORT_DATAGRAM, authmode, p_ca_certs);
ERR_FAIL_COND_V(err != OK, err);
mbedtls_ssl_set_hostname(ssl_ctx->get_context(), p_for_hostname.utf8().get_data());
mbedtls_ssl_set_bio(ssl_ctx->get_context(), this, bio_send, bio_recv, NULL);
mbedtls_ssl_set_timer_cb(ssl_ctx->get_context(), &timer, mbedtls_timing_set_delay, mbedtls_timing_get_delay);
status = STATUS_HANDSHAKING;
if ((ret = _do_handshake()) != OK) {
status = STATUS_ERROR_HOSTNAME_MISMATCH;
return FAILED;
}
return OK;
}
Error PacketPeerMbedDTLS::accept_peer(Ref<PacketPeerUDP> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain, Ref<CookieContextMbedTLS> p_cookies) {
Error err = ssl_ctx->init_server(MBEDTLS_SSL_TRANSPORT_DATAGRAM, MBEDTLS_SSL_VERIFY_NONE, p_key, p_cert, p_cookies);
ERR_FAIL_COND_V(err != OK, err);
base = p_base;
base->set_blocking_mode(false);
mbedtls_ssl_session_reset(ssl_ctx->get_context());
int ret = _set_cookie();
if (ret != 0) {
_cleanup();
ERR_FAIL_V_MSG(FAILED, "Error setting DTLS client cookie");
}
mbedtls_ssl_set_bio(ssl_ctx->get_context(), this, bio_send, bio_recv, NULL);
mbedtls_ssl_set_timer_cb(ssl_ctx->get_context(), &timer, mbedtls_timing_set_delay, mbedtls_timing_get_delay);
status = STATUS_HANDSHAKING;
if ((ret = _do_handshake()) != OK) {
status = STATUS_ERROR;
return FAILED;
}
return OK;
}
Error PacketPeerMbedDTLS::put_packet(const uint8_t *p_buffer, int p_bytes) {
ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED);
if (p_bytes == 0)
return OK;
int ret = mbedtls_ssl_write(ssl_ctx->get_context(), p_buffer, p_bytes);
if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
ret = 0; // non blocking io
} else if (ret <= 0) {
SSLContextMbedTLS::print_mbedtls_error(ret);
_cleanup();
return ERR_CONNECTION_ERROR;
}
return OK;
}
Error PacketPeerMbedDTLS::get_packet(const uint8_t **r_buffer, int &r_bytes) {
ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED);
r_bytes = 0;
int ret = mbedtls_ssl_read(ssl_ctx->get_context(), packet_buffer, PACKET_BUFFER_SIZE);
if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
ret = 0; // non blocking io
} else if (ret <= 0) {
if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
// Also send close notify back
disconnect_from_peer();
} else {
_cleanup();
status = STATUS_ERROR;
SSLContextMbedTLS::print_mbedtls_error(ret);
}
return ERR_CONNECTION_ERROR;
}
*r_buffer = packet_buffer;
r_bytes = ret;
return OK;
}
void PacketPeerMbedDTLS::poll() {
if (status == STATUS_HANDSHAKING) {
_do_handshake();
return;
} else if (status != STATUS_CONNECTED) {
return;
}
ERR_FAIL_COND(!base.is_valid());
int ret = mbedtls_ssl_read(ssl_ctx->get_context(), NULL, 0);
if (ret < 0 && ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
// Also send close notify back
disconnect_from_peer();
} else {
_cleanup();
status = STATUS_ERROR;
SSLContextMbedTLS::print_mbedtls_error(ret);
}
}
}
int PacketPeerMbedDTLS::get_available_packet_count() const {
ERR_FAIL_COND_V(status != STATUS_CONNECTED, 0);
return mbedtls_ssl_get_bytes_avail(&(ssl_ctx->ssl)) > 0 ? 1 : 0;
}
int PacketPeerMbedDTLS::get_max_packet_size() const {
return 488; // 512 (UDP in Godot) - 24 (DTLS header)
}
PacketPeerMbedDTLS::PacketPeerMbedDTLS() {
ssl_ctx.instance();
status = STATUS_DISCONNECTED;
}
PacketPeerMbedDTLS::~PacketPeerMbedDTLS() {
disconnect_from_peer();
}
void PacketPeerMbedDTLS::disconnect_from_peer() {
if (status != STATUS_CONNECTED && status != STATUS_HANDSHAKING)
return;
if (status == STATUS_CONNECTED) {
int ret = 0;
// Send SSL close notification, blocking, but ignore other errors.
do
ret = mbedtls_ssl_close_notify(ssl_ctx->get_context());
while (ret == MBEDTLS_ERR_SSL_WANT_WRITE);
}
_cleanup();
}
PacketPeerMbedDTLS::Status PacketPeerMbedDTLS::get_status() const {
return status;
}
PacketPeerDTLS *PacketPeerMbedDTLS::_create_func() {
return memnew(PacketPeerMbedDTLS);
}
void PacketPeerMbedDTLS::initialize_dtls() {
_create = _create_func;
available = true;
}
void PacketPeerMbedDTLS::finalize_dtls() {
_create = NULL;
available = false;
}

View file

@ -0,0 +1,88 @@
/*************************************************************************/
/* packet_peer_mbed_dtls.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 PACKET_PEER_MBED_DTLS_H
#define PACKET_PEER_MBED_DTLS_H
#include "core/io/packet_peer_dtls.h"
#include "ssl_context_mbedtls.h"
#include <mbedtls/timing.h>
class PacketPeerMbedDTLS : public PacketPeerDTLS {
private:
enum {
PACKET_BUFFER_SIZE = 65536
};
uint8_t packet_buffer[PACKET_BUFFER_SIZE];
Status status;
String hostname;
Ref<PacketPeerUDP> base;
static PacketPeerDTLS *_create_func();
static int bio_recv(void *ctx, unsigned char *buf, size_t len);
static int bio_send(void *ctx, const unsigned char *buf, size_t len);
void _cleanup();
protected:
Ref<SSLContextMbedTLS> ssl_ctx;
mbedtls_timing_delay_context timer;
static void _bind_methods();
Error _do_handshake();
int _set_cookie();
public:
virtual void poll();
virtual Error accept_peer(Ref<PacketPeerUDP> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert = Ref<X509Certificate>(), Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>(), Ref<CookieContextMbedTLS> p_cookies = Ref<CookieContextMbedTLS>());
virtual Error connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_validate_certs = false, const String &p_for_hostname = String(), Ref<X509Certificate> p_ca_certs = Ref<X509Certificate>());
virtual Status get_status() const;
virtual void disconnect_from_peer();
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size);
virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size);
virtual int get_available_packet_count() const;
virtual int get_max_packet_size() const;
static void initialize_dtls();
static void finalize_dtls();
PacketPeerMbedDTLS();
~PacketPeerMbedDTLS();
};
#endif // PACKET_PEER_MBED_DTLS_H

View file

@ -31,16 +31,22 @@
#include "register_types.h"
#include "crypto_mbedtls.h"
#include "dtls_server_mbedtls.h"
#include "packet_peer_mbed_dtls.h"
#include "stream_peer_mbedtls.h"
void register_mbedtls_types() {
CryptoMbedTLS::initialize_crypto();
StreamPeerMbedTLS::initialize_ssl();
PacketPeerMbedDTLS::initialize_dtls();
DTLSServerMbedTLS::initialize();
}
void unregister_mbedtls_types() {
DTLSServerMbedTLS::finalize();
PacketPeerMbedDTLS::finalize_dtls();
StreamPeerMbedTLS::finalize_ssl();
CryptoMbedTLS::finalize_crypto();
}

View file

@ -38,6 +38,53 @@ static void my_debug(void *ctx, int level,
fflush(stdout);
}
void SSLContextMbedTLS::print_mbedtls_error(int p_ret) {
printf("mbedtls error: returned -0x%x\n\n", -p_ret);
fflush(stdout);
}
/// CookieContextMbedTLS
Error CookieContextMbedTLS::setup() {
ERR_FAIL_COND_V_MSG(inited, ERR_ALREADY_IN_USE, "This cookie context is already in use");
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
mbedtls_ssl_cookie_init(&cookie_ctx);
inited = true;
int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0);
if (ret != 0) {
clear(); // Never leave unusable resources around.
ERR_FAIL_V_MSG(FAILED, "mbedtls_ctr_drbg_seed returned an error " + itos(ret));
}
ret = mbedtls_ssl_cookie_setup(&cookie_ctx, mbedtls_ctr_drbg_random, &ctr_drbg);
if (ret != 0) {
clear();
ERR_FAIL_V_MSG(FAILED, "mbedtls_ssl_cookie_setup returned an error " + itos(ret));
}
return OK;
}
void CookieContextMbedTLS::clear() {
if (!inited)
return;
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
mbedtls_ssl_cookie_free(&cookie_ctx);
}
CookieContextMbedTLS::CookieContextMbedTLS() {
inited = false;
}
CookieContextMbedTLS::~CookieContextMbedTLS() {
clear();
}
/// SSLContextMbedTLS
Error SSLContextMbedTLS::_setup(int p_endpoint, int p_transport, int p_authmode) {
ERR_FAIL_COND_V_MSG(inited, ERR_ALREADY_IN_USE, "This SSL context is already active");
@ -50,7 +97,7 @@ Error SSLContextMbedTLS::_setup(int p_endpoint, int p_transport, int p_authmode)
int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0);
if (ret != 0) {
clear(); // Never leave unusable resources around.
ERR_FAIL_V_MSG(FAILED, "mbedtls_ctr_drbg_seed returned an error" + itos(ret));
ERR_FAIL_V_MSG(FAILED, "mbedtls_ctr_drbg_seed returned an error " + itos(ret));
}
ret = mbedtls_ssl_config_defaults(&conf, p_endpoint, p_transport, MBEDTLS_SSL_PRESET_DEFAULT);
@ -64,7 +111,7 @@ Error SSLContextMbedTLS::_setup(int p_endpoint, int p_transport, int p_authmode)
return OK;
}
Error SSLContextMbedTLS::init_server(int p_transport, int p_authmode, Ref<CryptoKeyMbedTLS> p_pkey, Ref<X509CertificateMbedTLS> p_cert) {
Error SSLContextMbedTLS::init_server(int p_transport, int p_authmode, Ref<CryptoKeyMbedTLS> p_pkey, Ref<X509CertificateMbedTLS> p_cert, Ref<CookieContextMbedTLS> p_cookies) {
ERR_FAIL_COND_V(!p_pkey.is_valid(), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(!p_cert.is_valid(), ERR_INVALID_PARAMETER);
@ -89,6 +136,15 @@ Error SSLContextMbedTLS::init_server(int p_transport, int p_authmode, Ref<Crypto
if (certs->cert.next) {
mbedtls_ssl_conf_ca_chain(&conf, certs->cert.next, NULL);
}
// DTLS Cookies
if (p_transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) {
if (p_cookies.is_null() || !p_cookies->inited) {
clear();
ERR_FAIL_V(ERR_BUG);
}
cookies = p_cookies;
mbedtls_ssl_conf_dtls_cookies(&conf, mbedtls_ssl_cookie_write, mbedtls_ssl_cookie_check, &(cookies->cookie_ctx));
}
mbedtls_ssl_setup(&ssl, &conf);
return OK;
}
@ -134,6 +190,7 @@ void SSLContextMbedTLS::clear() {
if (pkey.is_valid())
pkey->unlock();
pkey = Ref<CryptoKeyMbedTLS>();
cookies = Ref<CookieContextMbedTLS>();
inited = false;
}

View file

@ -42,6 +42,27 @@
#include <mbedtls/debug.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ssl.h>
#include <mbedtls/ssl_cookie.h>
class SSLContextMbedTLS;
class CookieContextMbedTLS : public Reference {
friend class SSLContextMbedTLS;
protected:
bool inited;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_cookie_ctx cookie_ctx;
public:
Error setup();
void clear();
CookieContextMbedTLS();
~CookieContextMbedTLS();
};
class SSLContextMbedTLS : public Reference {
@ -51,16 +72,19 @@ protected:
static PoolByteArray _read_file(String p_path);
public:
static void print_mbedtls_error(int p_ret);
Ref<X509CertificateMbedTLS> certs;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
Ref<CookieContextMbedTLS> cookies;
Ref<CryptoKeyMbedTLS> pkey;
Error _setup(int p_endpoint, int p_transport, int p_authmode);
Error init_server(int p_transport, int p_authmode, Ref<CryptoKeyMbedTLS> p_pkey, Ref<X509CertificateMbedTLS> p_cert);
Error init_server(int p_transport, int p_authmode, Ref<CryptoKeyMbedTLS> p_pkey, Ref<X509CertificateMbedTLS> p_cert, Ref<CookieContextMbedTLS> p_cookies = Ref<CookieContextMbedTLS>());
Error init_client(int p_transport, int p_authmode, Ref<X509CertificateMbedTLS> p_valid_cas);
void clear();

View file

@ -33,11 +33,6 @@
#include "core/io/stream_peer_tcp.h"
#include "core/os/file_access.h"
void _print_error(int ret) {
printf("mbedtls error: returned -0x%x\n\n", -ret);
fflush(stdout);
}
int StreamPeerMbedTLS::bio_send(void *ctx, const unsigned char *buf, size_t len) {
if (buf == NULL || len <= 0) return 0;
@ -88,8 +83,8 @@ Error StreamPeerMbedTLS::_do_handshake() {
while ((ret = mbedtls_ssl_handshake(ssl_ctx->get_context())) != 0) {
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
// An error occurred.
ERR_PRINTS("TLS handshake error: " + itos(ret));
_print_error(ret);
ERR_PRINT("TLS handshake error: " + itos(ret));
SSLContextMbedTLS::print_mbedtls_error(ret);
disconnect_from_stream();
status = STATUS_ERROR;
return FAILED;
@ -188,7 +183,7 @@ Error StreamPeerMbedTLS::put_partial_data(const uint8_t *p_data, int p_bytes, in
disconnect_from_stream();
return ERR_FILE_EOF;
} else if (ret <= 0) {
_print_error(ret);
SSLContextMbedTLS::print_mbedtls_error(ret);
disconnect_from_stream();
return ERR_CONNECTION_ERROR;
}
@ -233,7 +228,7 @@ Error StreamPeerMbedTLS::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r
disconnect_from_stream();
return ERR_FILE_EOF;
} else if (ret <= 0) {
_print_error(ret);
SSLContextMbedTLS::print_mbedtls_error(ret);
disconnect_from_stream();
return ERR_CONNECTION_ERROR;
}
@ -264,7 +259,7 @@ void StreamPeerMbedTLS::poll() {
disconnect_from_stream();
return;
} else if (ret < 0) {
_print_error(ret);
SSLContextMbedTLS::print_mbedtls_error(ret);
disconnect_from_stream();
return;
}

View file

@ -585,6 +585,8 @@ ENET_API void enet_host_channel_limit (ENetHost *, size_t);
ENET_API void enet_host_bandwidth_limit (ENetHost *, enet_uint32, enet_uint32);
extern void enet_host_bandwidth_throttle (ENetHost *);
extern enet_uint32 enet_host_random_seed (void);
ENET_API void enet_host_dtls_server_setup (ENetHost *, void *, void *);
ENET_API void enet_host_dtls_client_setup (ENetHost *, void *, uint8_t, const char *);
ENET_API int enet_peer_send (ENetPeer *, enet_uint8, ENetPacket *);
ENET_API ENetPacket * enet_peer_receive (ENetPeer *, enet_uint8 * channelID);

View file

@ -32,13 +32,313 @@
@brief ENet Godot specific functions
*/
#include "core/io/dtls_server.h"
#include "core/io/ip.h"
#include "core/io/net_socket.h"
#include "core/io/packet_peer_dtls.h"
#include "core/io/udp_server.h"
#include "core/os/os.h"
// This must be last for windows to compile (tested with MinGW)
#include "enet/enet.h"
/// Abstract ENet interface for UDP/DTLS.
class ENetGodotSocket {
public:
virtual Error bind(IP_Address p_ip, uint16_t p_port) = 0;
virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IP_Address p_ip, uint16_t p_port) = 0;
virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IP_Address &r_ip, uint16_t &r_port) = 0;
virtual int set_option(ENetSocketOption p_option, int p_value) = 0;
virtual void close() = 0;
virtual ~ENetGodotSocket(){};
};
class ENetDTLSClient;
class ENetDTLSServer;
/// NetSocket interface
class ENetUDP : public ENetGodotSocket {
friend class ENetDTLSClient;
friend class ENetDTLSServer;
private:
Ref<NetSocket> sock;
IP_Address address;
uint16_t port;
bool bound;
public:
ENetUDP() {
sock = Ref<NetSocket>(NetSocket::create());
IP::Type ip_type = IP::TYPE_ANY;
bound = false;
sock->open(NetSocket::TYPE_UDP, ip_type);
}
~ENetUDP() {
sock->close();
}
Error bind(IP_Address p_ip, uint16_t p_port) {
address = p_ip;
port = p_port;
bound = true;
return sock->bind(address, port);
}
Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IP_Address p_ip, uint16_t p_port) {
return sock->sendto(p_buffer, p_len, r_sent, p_ip, p_port);
}
Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IP_Address &r_ip, uint16_t &r_port) {
Error err = sock->poll(NetSocket::POLL_TYPE_IN, 0);
if (err != OK)
return err;
return sock->recvfrom(p_buffer, p_len, r_read, r_ip, r_port);
}
int set_option(ENetSocketOption p_option, int p_value) {
switch (p_option) {
case ENET_SOCKOPT_NONBLOCK: {
sock->set_blocking_enabled(p_value ? false : true);
return 0;
} break;
case ENET_SOCKOPT_BROADCAST: {
sock->set_broadcasting_enabled(p_value ? true : false);
return 0;
} break;
case ENET_SOCKOPT_REUSEADDR: {
sock->set_reuse_address_enabled(p_value ? true : false);
return 0;
} break;
case ENET_SOCKOPT_RCVBUF: {
return -1;
} break;
case ENET_SOCKOPT_SNDBUF: {
return -1;
} break;
case ENET_SOCKOPT_RCVTIMEO: {
return -1;
} break;
case ENET_SOCKOPT_SNDTIMEO: {
return -1;
} break;
case ENET_SOCKOPT_NODELAY: {
sock->set_tcp_no_delay_enabled(p_value ? true : false);
return 0;
} break;
}
return -1;
}
void close() {
sock->close();
}
};
/// DTLS Client ENet interface
class ENetDTLSClient : public ENetGodotSocket {
bool connected;
Ref<PacketPeerUDP> udp;
Ref<PacketPeerDTLS> dtls;
bool verify;
String for_hostname;
Ref<X509Certificate> cert;
public:
ENetDTLSClient(ENetUDP *p_base, Ref<X509Certificate> p_cert, bool p_verify, String p_for_hostname) {
verify = p_verify;
for_hostname = p_for_hostname;
cert = p_cert;
udp.instance();
dtls = Ref<PacketPeerDTLS>(PacketPeerDTLS::create());
p_base->close();
if (p_base->bound) {
bind(p_base->address, p_base->port);
}
connected = false;
}
~ENetDTLSClient() {
close();
}
Error bind(IP_Address p_ip, uint16_t p_port) {
return udp->listen(p_port, p_ip);
}
Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IP_Address p_ip, uint16_t p_port) {
if (!connected) {
udp->connect_to_host(p_ip, p_port);
dtls->connect_to_peer(udp, verify, for_hostname, cert);
connected = true;
}
dtls->poll();
if (dtls->get_status() == PacketPeerDTLS::STATUS_HANDSHAKING)
return ERR_BUSY;
else if (dtls->get_status() != PacketPeerDTLS::STATUS_CONNECTED)
return FAILED;
r_sent = p_len;
return dtls->put_packet(p_buffer, p_len);
}
Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IP_Address &r_ip, uint16_t &r_port) {
dtls->poll();
if (dtls->get_status() == PacketPeerDTLS::STATUS_HANDSHAKING)
return ERR_BUSY;
if (dtls->get_status() != PacketPeerDTLS::STATUS_CONNECTED)
return FAILED;
int pc = dtls->get_available_packet_count();
if (pc == 0)
return ERR_BUSY;
else if (pc < 0)
return FAILED;
const uint8_t *buffer;
Error err = dtls->get_packet(&buffer, r_read);
ERR_FAIL_COND_V(err != OK, err);
ERR_FAIL_COND_V(p_len < r_read, ERR_OUT_OF_MEMORY);
copymem(p_buffer, buffer, r_read);
r_ip = udp->get_packet_address();
r_port = udp->get_packet_port();
return err;
}
int set_option(ENetSocketOption p_option, int p_value) {
return -1;
}
void close() {
dtls->disconnect_from_peer();
udp->close();
}
};
/// DTLSServer - ENet interface
class ENetDTLSServer : public ENetGodotSocket {
Ref<DTLSServer> server;
Ref<UDPServer> udp_server;
Map<String, Ref<PacketPeerDTLS> > peers;
int last_service;
public:
ENetDTLSServer(ENetUDP *p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert) {
last_service = 0;
udp_server.instance();
p_base->close();
if (p_base->bound) {
bind(p_base->address, p_base->port);
}
server = Ref<DTLSServer>(DTLSServer::create());
server->setup(p_key, p_cert);
}
~ENetDTLSServer() {
close();
}
Error bind(IP_Address p_ip, uint16_t p_port) {
return udp_server->listen(p_port, p_ip);
}
Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IP_Address p_ip, uint16_t p_port) {
String key = String(p_ip) + ":" + itos(p_port);
ERR_FAIL_COND_V(!peers.has(key), ERR_UNAVAILABLE);
Ref<PacketPeerDTLS> peer = peers[key];
Error err = peer->put_packet(p_buffer, p_len);
if (err == OK)
r_sent = p_len;
else if (err == ERR_BUSY)
r_sent = 0;
else
r_sent = -1;
return err;
}
Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IP_Address &r_ip, uint16_t &r_port) {
// TODO limits? Maybe we can better enforce allowed connections!
if (udp_server->is_connection_available()) {
Ref<PacketPeerUDP> udp = udp_server->take_connection();
IP_Address peer_ip = udp->get_packet_address();
int peer_port = udp->get_packet_port();
Ref<PacketPeerDTLS> peer = server->take_connection(udp);
PacketPeerDTLS::Status status = peer->get_status();
if (status == PacketPeerDTLS::STATUS_HANDSHAKING || status == PacketPeerDTLS::STATUS_CONNECTED) {
String key = String(peer_ip) + ":" + itos(peer_port);
peers[key] = peer;
}
}
List<String> remove;
Error err = ERR_BUSY;
// TODO this needs to be fair!
for (Map<String, Ref<PacketPeerDTLS> >::Element *E = peers.front(); E; E = E->next()) {
Ref<PacketPeerDTLS> peer = E->get();
peer->poll();
if (peer->get_status() == PacketPeerDTLS::STATUS_HANDSHAKING)
continue;
else if (peer->get_status() != PacketPeerDTLS::STATUS_CONNECTED) {
// Peer disconnected, removing it.
remove.push_back(E->key());
continue;
}
if (peer->get_available_packet_count() > 0) {
const uint8_t *buffer;
err = peer->get_packet(&buffer, r_read);
if (err != OK || p_len < r_read) {
// Something wrong with this peer, removing it.
remove.push_back(E->key());
err = FAILED;
continue;
}
Vector<String> s = E->key().rsplit(":", false, 1);
ERR_CONTINUE(s.size() != 2); // BUG!
copymem(p_buffer, buffer, r_read);
r_ip = s[0];
r_port = s[1].to_int();
break; // err = OK
}
}
// Remove disconnected peers from map.
for (List<String>::Element *E = remove.front(); E; E = E->next()) {
peers.erase(E->get());
}
return err; // OK, ERR_BUSY, or possibly an error.
}
int set_option(ENetSocketOption p_option, int p_value) {
return -1;
}
void close() {
for (Map<String, Ref<PacketPeerDTLS> >::Element *E = peers.front(); E; E = E->next()) {
E->get()->disconnect_from_peer();
}
peers.clear();
udp_server->stop();
server->stop();
}
};
static enet_uint32 timeBase = 0;
int enet_initialize(void) {
@ -92,13 +392,23 @@ int enet_address_get_host(const ENetAddress *address, char *name, size_t nameLen
ENetSocket enet_socket_create(ENetSocketType type) {
NetSocket *socket = NetSocket::create();
IP::Type ip_type = IP::TYPE_ANY;
socket->open(NetSocket::TYPE_UDP, ip_type);
ENetUDP *socket = memnew(ENetUDP);
return socket;
}
void enet_host_dtls_server_setup(ENetHost *host, void *p_key, void *p_cert) {
ENetUDP *sock = (ENetUDP *)host->socket;
host->socket = memnew(ENetDTLSServer(sock, Ref<CryptoKey>((CryptoKey *)p_key), Ref<X509Certificate>((X509Certificate *)p_cert)));
memdelete(sock);
}
void enet_host_dtls_client_setup(ENetHost *host, void *p_cert, uint8_t p_verify, const char *p_for_hostname) {
ENetUDP *sock = (ENetUDP *)host->socket;
host->socket = memnew(ENetDTLSClient(sock, Ref<X509Certificate>((X509Certificate *)p_cert), p_verify, String(p_for_hostname)));
memdelete(sock);
}
int enet_socket_bind(ENetSocket socket, const ENetAddress *address) {
IP_Address ip;
@ -108,7 +418,7 @@ int enet_socket_bind(ENetSocket socket, const ENetAddress *address) {
ip.set_ipv6(address->host);
}
NetSocket *sock = (NetSocket *)socket;
ENetGodotSocket *sock = (ENetGodotSocket *)socket;
if (sock->bind(ip, address->port) != OK) {
return -1;
}
@ -116,7 +426,7 @@ int enet_socket_bind(ENetSocket socket, const ENetAddress *address) {
}
void enet_socket_destroy(ENetSocket socket) {
NetSocket *sock = (NetSocket *)socket;
ENetGodotSocket *sock = (ENetGodotSocket *)socket;
sock->close();
memdelete(sock);
}
@ -125,7 +435,7 @@ int enet_socket_send(ENetSocket socket, const ENetAddress *address, const ENetBu
ERR_FAIL_COND_V(address == NULL, -1);
NetSocket *sock = (NetSocket *)socket;
ENetGodotSocket *sock = (ENetGodotSocket *)socket;
IP_Address dest;
Error err;
size_t i = 0;
@ -167,15 +477,7 @@ int enet_socket_receive(ENetSocket socket, ENetAddress *address, ENetBuffer *buf
ERR_FAIL_COND_V(bufferCount != 1, -1);
NetSocket *sock = (NetSocket *)socket;
Error ret = sock->poll(NetSocket::POLL_TYPE_IN, 0);
if (ret == ERR_BUSY)
return 0;
if (ret != OK)
return -1;
ENetGodotSocket *sock = (ENetGodotSocket *)socket;
int read;
IP_Address ip;
@ -215,47 +517,8 @@ int enet_socket_listen(ENetSocket socket, int backlog) {
int enet_socket_set_option(ENetSocket socket, ENetSocketOption option, int value) {
NetSocket *sock = (NetSocket *)socket;
switch (option) {
case ENET_SOCKOPT_NONBLOCK: {
sock->set_blocking_enabled(value ? false : true);
return 0;
} break;
case ENET_SOCKOPT_BROADCAST: {
sock->set_broadcasting_enabled(value ? true : false);
return 0;
} break;
case ENET_SOCKOPT_REUSEADDR: {
sock->set_reuse_address_enabled(value ? true : false);
return 0;
} break;
case ENET_SOCKOPT_RCVBUF: {
return -1;
} break;
case ENET_SOCKOPT_SNDBUF: {
return -1;
} break;
case ENET_SOCKOPT_RCVTIMEO: {
return -1;
} break;
case ENET_SOCKOPT_SNDTIMEO: {
return -1;
} break;
case ENET_SOCKOPT_NODELAY: {
sock->set_tcp_no_delay_enabled(value ? true : false);
return 0;
} break;
}
return -1;
ENetGodotSocket *sock = (ENetGodotSocket *)socket;
return sock->set_option(option, value);
}
int enet_socket_get_option(ENetSocket socket, ENetSocketOption option, int *value) {