Hosting a multiplayer web game for LAN only. For a Jackbox like multiplayer

Disclaimer

I know this is a bit long, but I want to give people that want to help as much info on what I tried / want to do as possible.

Background / Why

I am trying to make a game that is a bit like Munchkin, For The King or maybe DND, but with controls similar to Jackbox games. Basically, one would start the game on PC where you set up the game with the rules/map you want, and then the players join with their phones on a website.
The players would basically have their character, inventory and abilities on their phone where they control what they want to do, with the battles and map being shown on the PC.

My I Idea would be to use Godots WebSocket Multiplayer with the PC build acting as the server. The build for the clients would be a web build that would have to be hosted by the server build / on the PC.

The problem

And this is my problem. How would I best set up the PC build to launch the web build so that people in the local network can start the game in their browser.
I have tried Caddy to serve the web build. This works when serving on http://localhost and using non-secure WebSockets ws://. This is what I would like to achieve, but having it available to everyone on the LAN.

The Caddyfile for working localhost server
http://localhost
 
file_server browse {
	index Multiplayer_Test.html
}
header {
	# these headers are required or godot shouts at you
	Cross-Origin-Opener-Policy "same-origin"
	Cross-Origin-Embedder-Policy "require-corp"
}

What I’ve tried so far

Hosting to LAN (HTTP)

When I use the same configuration as before for localhost but use http://my.local.ip.address Godot yells at me with:


With the important part being: Secure Context ... (use HTTPS) which would mean that I need to serve the web game over HTTPS instead of HTTP. This in turn means, that I can’t use ws:// but need to use the secure WebSockets wss:// for which I need a certificate.

Possible way forward

Is it possible to turn off the requirement to use HTTPS? Since it’s just for game data on the LAN, it doesn’t need to be encrypted, I would think. This would probably fix my problem if possible.

Creating a certificate to use wss://

As I could not find anything about hosting a Godot web export without HTTPS I tried switching the networking over to use TLS/wss://.
For this, I followed this tutorial to create self-signed certificates with Godot and converted it to Godot 4, resulting in the following script:

X509 self-signed certificate generator
extends Node

# https://www.youtube.com/watch?v=gcopx40pwvY
var x509_cert_filename = "X509_Certificate.crt"
var x509_key_filename = "x509_Key.key"
@onready var X509_cert_path = "user://Certificate/" + x509_cert_filename
@onready var X509_key_path = "user://Certificate/" + x509_key_filename

var CN = "192.168.178.37" # nameserver
var O = "Gaweringo" # organization
var C = "AT" # country
var not_before = "20240210000000"
var not_after = "20250210000000"

# Called when the node enters the scene tree for the first time.
func _ready():
    if DirAccess.dir_exists_absolute("user://Certificate"):
        pass
    else:
        DirAccess.make_dir_absolute("user://Certificate")
    CreateX509Cert()
    print("Certificate Created")

func CreateX509Cert():
    var CNOC = "CN=" + CN + " ,O=" + O + ",C=" + C
    var crypto = Crypto.new()
    var crypto_key = crypto.generate_rsa(4096)
    var X509_cert = crypto.generate_self_signed_certificate(crypto_key, CNOC, not_before, not_after)
    X509_cert.save(X509_cert_path)
    crypto_key.save(X509_key_path)

I then used the generated certificate in the multiplayer implementation:

The basic multiplayer code
func _on_host_pressed():
    var server = WebSocketMultiplayerPeer.new()
    var error = server.create_server(6942, "*", TLSOptions.server(key, cert))
    if error != OK:
        print("Problem creating server:" + str(error))
        return
    multiplayer.set_multiplayer_peer(server)
    print("server done")


func _on_join_pressed():
    var peer = WebSocketMultiplayerPeer.new()
    var error = peer.create_client("wss://192.168.178.37:6942", TLSOptions.client(cert))
    if error != OK:
        print("failed to join: " + str(error))
        return
    multiplayer.set_multiplayer_peer(peer)

But when I try to use this implementation, the connection from the client to the server fails, with the following error showing up in the debugger: _do_handshake: TLS handshake error: -9984 when I run two Windows builds and WebSocket connection to 'wss://192.168.178.37:6942/' failed on the web build in Chrome.
From this discussion, it seems like there might be a problem with my certificates, but I can’t really think of what I could do differently. Or more specifically, the solutions in this discussion seems to be to not use an IP address, but I can’t have a domain name on LAN.

The strange thing is, that if I use Postman to try the WebSocket connection it actually connects, even from a different PC on the LAN, and it shows up as a new client having connected to the server.

Well, at least it did, until I just checked it again while writing this part. Now I am getting this error in Postman:

Postman wss connection error
Error: write EPROTO 124881672:error:0400006b:RSA routines:OPENSSL_internal:BLOCK_TYPE_IS_NOT_01:../../../../src/third_party/boringssl/src/crypto/fipsmodule/rsa/padding.c:108:
124881672:error:04000088:RSA routines:OPENSSL_internal:PADDING_CHECK_FAILED:../../../../src/third_party/boringssl/src/crypto/fipsmodule/rsa/rsa_impl.c:676:
124881672:error:10000072:SSL routines:OPENSSL_internal:BAD_SIGNATURE:../../../../src/third_party/boringssl/src/ssl/handshake_client.cc:1191:

With this one, there seem to be so many things that could be a problem, that I don’t really know how to check and don’t really know where to start fixing things.

Wrapping up

Is what I’m trying to do here even the right approach?
If not, what would be some of your ideas that I could try?
If it is, how can I make this work?
Thanks a lot for reading this long cry for help ; )

I can’t say for sure about the https stuff but the PC host website is going to be tough without a custom router setup to perform local DNS for you.

So you are pretty much stuck with the IP. Unless your router has UPnP enabled. Then you can just use the name of the PC and your router will auto resolve your host IP.

Using IP addresses is ok for me, as I can just create a QR-Code that links to the correct IP, so users wouldn’t even have to enter it manually.

Where I got to by now

I’ve been trying out some things and I got https with wss working somewhat. At least in Chrome.
I use the X509_Generator as described above to generate a new set of Key and Certificate when the Server is started (clicking host on the PC). Then I use them when creating the WebSocket server with TLSOptions.server(key, cert).
To serve the web version for the clients/players I found godot_web by pipejakob which can take a TLS certificate and key to host the web version for everyone on the LAN.

When someone now goes to https://game.server.ip.addr:8000 the get a warning about the server using a self-signed certificate (to be expected) but after they accept they can load the game and connect to the game server.

The second part here is still not optimal. I only have gotten the connectiong to work by using TLSOptions.client_unsafe() which does not require validation of the certificate, but is also stated to be not recommended for purposes other than testing.

Another Problem with this somewhat working version is, that it only works this way on Chrome. On Firefox I still can’t connect, as Firefox seemingly does not allow the connection to the WebSocket, as it counts the Certificate as not valid, until it is manually approved.
Meaning that if one manually goes to the WebSocket connection but replaces wss:// with https:// so wss://server.ip.addr:websocket_port becomes https://server.ip.addr:websocket_port and then accepts the security warning about self-signed certificates on that site the connection to the game server over the WebSocket can then be established.

Two problems remain

  1. Having Firefox accept the wss:// connection that uses the same certificate as the website automatically.
  2. Getting TLSOptions.client(cert) to work. But this seems impossible at first glance, as there is a note on the TLSOptions documentatoin that TLS is always enforced against the CA list of the web browser, which I think would mean that a self-signed certificate will not work. Unless, accepting the self-signed certificate warning actually adds the self-signed cert to the CA list.
    I’m a bit confused about this note as the same note is also on the client_unsafe() function but that seems to work. I’ll have to look into this more.

I just posted to the Godot discord with the same issue.

Trying to make jackbox like game. I have the ‘controller’ hosted on AWS and seems to be working correctly. I produced the certificate and key for the server from this stack overflow answer https://stackoverflow.com/questions/50625283/how-to-install-openssl-in-windows-10 but I’m not entirely sure what to put in the clients certificate config. I tried with the same cert as the server as I also stumbled upon the same tutorial as you did and noticed they reused the same cert for the server as for the client.