ENet with DTLS encryption in 4.0
By: Fabio Alessandrelli 25 February 2020
This article is from February 2020, some of its contents might be outdated and no longer accurate.
You can find up-to-date information about the engine in the official documentation.
Hello Godotters, as part of my November work (sponsored, as always, by Mozilla) I’ve been working on finalizing DTLS support I wrote about in a previous report, and implementing a custom ENet socket layer that uses it.
This allows for optional, transparent, easy-to-use encryption of the high level Multiplayer API when using the ENet peer (
NetworkedMultiplayerENet). If you want to try these features in 3.2, and don’t mind compiling the engine yourself, check out the end of this post.
DTLS support is now merged in the
master branch. To know more about DTLS support in Godot 4.0 and DTLS in general you can read the previous blog post on the subject.
Additionally, documentation for all the new classes with small code samples has been added, the documentation is not yet synced with the online version, so here is a simple script (included in the docs) that shows how to make a DTLS server and client:
# 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() > 0: print("Received message from client: %s" % p.get_packet().get_string_from_utf8()) p.put_packet("Hello DTLS client".to_utf8())
# 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() > 0: print("Connected: %s" % dtls.get_packet().get_string_from_utf8()) connected = true
ENet DTLS support
With the new DTLS interface laid out, our custom ENet implementation has been updated to optionally use it to wrap all the ENet packets. This meant a lot of work under the hood (which actually took me most of December too), but with a very pleasing result for the end user. Now, the only thing to do to enable DTLS encryption on the ENet peer is setting the
use_dtls property to
true, and calling
set_dtls_certificate() with the appropriate server certificate (plus
set_dtls_key() for servers).
extends Node # Your DTLS server key (secret to the server). var key = load("key.key") # Your DTLS certificate (shared between server and clients). var cert = load("cert.crt") func start_server(): var peer = NetworkedMultiplayerENet.new() peer.use_dtls = true peer.set_dtls_key(key) peer.set_dtls_certificate(cert) peer.create_server(8092) multiplayer.set_network_peer(peer) func start_client(): var peer = NetworkedMultiplayerENet.new() peer.use_dtls = true # Optionally disable this to skip certificate verification (unsecure). peer.dtls_verify = true peer.set_dtls_certificate(cert) peer.create_client("localhost", 8092) multiplayer.set_network_peer(peer)
Here you can see a screenshot of the result of sending an RPC with and without DTLS enabled:
The pull request for this work (merged in
If you want to try the new DTLS features, and you don’t mind compiling the engine, you can check out this 3.2 PR.