Skip to content

Commit

Permalink
doc: Add netfox.noray docs (#116)
Browse files Browse the repository at this point in the history
Closes #98
  • Loading branch information
elementbound authored Nov 18, 2023
1 parent 3d5981e commit 1b5205d
Show file tree
Hide file tree
Showing 34 changed files with 278 additions and 157 deletions.
2 changes: 1 addition & 1 deletion addons/netfox.extras/plugin.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
name="netfox.extras"
description="Game-specific utilities for Netfox"
author="Tamas Galffy"
version="0.15.10"
version="0.15.11"
script="netfox-extras.gd"
130 changes: 2 additions & 128 deletions addons/netfox.noray/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,137 +25,10 @@ TBA

## Usage

See the docs ( TBA ).
See the [docs].

For a full example, see [noray-bootstrapper.gd].

### Setup

All players must connect to noray and register:

```gdscript
var host = "some.noray.host"
var port = 8890
var err = OK
# Connect to noray
err = await Noray.connect_to_host(host, port)
if err != OK:
return err # Failed to connect
# Register host
Noray.register_host()
await Noray.on_pid
# Register remote address
# This is where noray will direct traffic
err = await Noray.register_remote()
if err != OK:
return err # Failed to register
```

Once this process is done, noray has assigned two ID's to the player: an OpenID
and a PrivateID. The OpenID is used to identify the player, e.g. when others
want to connect, and is freely shareable. The PrivateID is used by the client
to identify itself towards noray, and should not be shared.

### Starting a host

To start hosting, use noray's registered local port:

```gdscript
var peer = ENetMultiplayerPeer.new()
var err = peer.create_server(Noray.local_port)
if err != OK:
return false # Failed to listen on port
```

### Starting a client

To connect to someone using noray, the target player's OpenID is needed. Note
that noray itself does not provide any facilities to share these.

For testing, simply displaying the OID for the player and sharing it over
messaging apps can work as a temporary solution.

```gdscript
var oid = "abcd1234"
# Connect using NAT punchthrough
Noray.connect_nat(oid)
# Or connect using relay
Noray.connect_relay(oid)
```

The above two calls will return instantly, since they only send a request to
noray. The actual connection will be orchestrated by noray, and games must
implement the related callbacks.

### Implementing callbacks

When noray receives a connection request ( either over NAT or relay ), it will
ask both participants to do a handshake. Once this handshake is successful,
players may connect to eachother.

This is why callbacks must be implemented.

Client example:

```gdscript
func _ready():
Noray.on_connect_nat.connect(_handle_connect)
Noray.on_connect_relay.connect(_handle_connect)
func _handle_connect(address: String, port: int) -> Error:
# Do a handshake
var udp = PacketPeerUDP.new()
udp.bind(Noray.local_port)
udp.set_dest_address(address, port)
var err = await PacketHandshake.over_packet_peer(udp)
udp.close()
if err != OK:
return err
# Connect to host
var peer = ENetMultiplayerPeer.new()
err = peer.create_client(address, port, 0, 0, 0, Noray.local_port)
if err != OK:
return err
return OK
```

> *Note:* Make sure to **always** specifiy the local port for the client - this
> is the only port noray recognizes, and failing to specify it will result in
> broken connectivity.
Host example:

```gdscript
func _ready():
Noray.on_connect_nat.connect(_handle_connect)
Noray.on_connect_relay.connect(_handle_connect)
func _handle_connect(address: String, port: int) -> Error:
var peer = get_tree().get_multiplayer().multiplayer_peer as ENetMultiplayerPeer
var err = await PacketHandshake.over_enet(peer.host, address, port)
if err != OK:
return err
return OK
```

> *Note:* The host handshake is a bit different, as it can't receive manual
> packets, only send them. So it assumes that the target is always responsive,
> and just blasts them with a bunch of packets. If the target is indeed
> responsive, it can connect. If not, nothing happens, as expected.
## License

netfox.noray is under the [MIT license](LICENSE).
Expand All @@ -167,4 +40,5 @@ In case of any issues, comments, or questions, please feel free to [open an issu
[netfox]: https://github.com/foxssake/netfox
[source]: https://github.com/foxssake/netfox/archive/refs/heads/main.zip
[noray]: https://github.com/foxssake/noray
[docs]: https://foxssake.github.io/netfox/netfox.noray/guides/noray/
[noray-bootstrapper.gd]: ../../examples/shared/scripts/noray-bootstrapper.gd
4 changes: 0 additions & 4 deletions addons/netfox.noray/noray.gd
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
extends Node
## A noray client for Godot.
##
## noray can be used - similarly to a STUN+TURN server - to orchestrate
## connection between two devices on the internet, either via NAT punchthrough,
## or if that's not possible, by acting as a relay.
##
## See: https://github.com/foxssake/noray

var _peer: StreamPeerTCP = StreamPeerTCP.new()
Expand Down
10 changes: 1 addition & 9 deletions addons/netfox.noray/packet-handshake.gd
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
extends Node
## This class implements a handshake protocol over for multiple classes.
##
## The point of the handshake itself is to confirm two-way connection between
## two parties - i.e. both parties can receive message from the other and
## receive acknowledgement from the other that their messages have arrived.
##
## This is an important step before establishing connection for actual game
## play, as this lets both the client's and server's routers ( if any ) know
## that traffic is expected and should be let through.
## This class implements a handshake protocol over UDP for multiple classes.

class HandshakeStatus:
var did_read: bool = false
Expand Down
2 changes: 1 addition & 1 deletion addons/netfox.noray/plugin.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
name="netfox.noray"
description="Bulletproof your connectivity with noray integration for netfox"
author="Tamas Galffy"
version="0.15.10"
version="0.15.11"
script="netfox-noray.gd"
2 changes: 1 addition & 1 deletion addons/netfox/plugin.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
name="netfox"
description="A higher-level networking addon with rollback support"
author="Tamas Galffy"
version="0.15.10"
version="0.15.11"
script="netfox.gd"
2 changes: 2 additions & 0 deletions docs/netfox.noray/assets/packet-handshake.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions docs/netfox.noray/assets/packet-handshake.uml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@startuml

actor "Player A" as A
boundary Internet as Net
actor "Player B" as B

A -> Net : "$-w-"
B -> Net: "$-w-"
A -> B : "$-w-"
B -> A : "$rw-"
A -> B : "$rw-"
B -> A : "$rwx"
A -> B : "$rwx"

@enduml
178 changes: 178 additions & 0 deletions docs/netfox.noray/guides/noray.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Noray

Singleton providing [noray] integration.

*noray* is a backend application that orchestrates connection between players.
To do this, players send a connection request to *noray*, and in turn *noray*
sends the players' external addresses to eachother. It is then up to the
players to conduct a handshake process.

If the handshake fails, players can request a *relay* from *noray*. In these
cases, *noray* will receive data from one player and forward it to the other,
acting as a middle man.

## Identifiers

*noray* identifies players with two different IDs: OpenID and PrivateID.

*OpenID* is public, and can be shared with other players. This ID is used to
identify hosts when connecting to games.

*PrivateID* is only sent to the player it identifies and should **never** be
shared. Acts similar to a password, and is used to authorize commands.

## Relays and NAT Punchthrough

*noray* provides two methods of connecting players.

*NAT Punchthrough* relies on the NAT table. Players must continuously send data
to eachother until either two-way communication is established, or a timeout is
reached. For certain router setups, NAT punchthrough does not work.

See: [NAT Punch-through for Multiplayer Games]

For *relays*, *noray* allocates a specific port to a given player. When *noray*
receives data on this port, it will forward it as-is to the player. As long as
*noray* is accessible over the internet, relays should work reliably no matter
the router setup.

## Registering with noray

To start using *noray*, connect to a *noray* server, request IDs by
registering, and then register the remote address:

```gdscript
var host = "some.noray.host"
var port = 8890
var err = OK
# Connect to noray
err = await Noray.connect_to_host(host, port)
if err != OK:
return err # Failed to connect
# Register host
Noray.register_host()
await Noray.on_pid
# Register remote address
# This is where noray will direct traffic
err = await Noray.register_remote()
if err != OK:
return err # Failed to register
```

By calling `Noray.register_host()`, a request is sent to *noray*. Once a
response is received, both the `on_pid` and `on_oid` signals are fired, for
receiving the PrivateID and OpenID respectively.

The remote address must be registered so that *noray* knows where to direct
other players wanting to connect. This process also sets `Noray.local_port`,
which is where traffic can be received through *noray*.

## Starting a host

To host a game, start listening on *noray*'s local port:

```gdscript
var peer = ENetMultiplayerPeer.new()
var err = peer.create_server(Noray.local_port)
if err != OK:
return false # Failed to listen on port
```

The rest is handled by *noray*.

## Starting a client

To connect to a game, send a request to *noray* with the host's OpenID.

```gdscript
var oid = "abcd1234"
# Connect using NAT punchthrough
Noray.connect_nat(oid)
# Or connect using relay
Noray.connect_relay(oid)
```

Once the request is sent, *noray* will send a message to both the client and
the host players to connect to each other. The actual connection is done by
handling signals.

> *Note* that *noray* provides no functionality to share OpenIDs. For
> development, you can display the OpenID in a textbox, letting players copy it
> and share over their preferred messaging app.
## Handling signals

When a connect message is received, the appropriate signal is fired.

*on_connect_nat* is fired to connect with NAT punchthrough.

*on_connect_relay* is fired to connect to a relay.

In both cases, a public address is passed to the signal handler, in the form of
an address string and a port. Handlers must conduct a handshake ( e.g. with
[PacketHandshake] ) and connect if successful.

Client example:

```gdscript
func _ready():
Noray.on_connect_nat.connect(_handle_connect)
Noray.on_connect_relay.connect(_handle_connect)
func _handle_connect(address: String, port: int) -> Error:
# Do a handshake
var udp = PacketPeerUDP.new()
udp.bind(Noray.local_port)
udp.set_dest_address(address, port)
var err = await PacketHandshake.over_packet_peer(udp)
udp.close()
if err != OK:
return err
# Connect to host
var peer = ENetMultiplayerPeer.new()
err = peer.create_client(address, port, 0, 0, 0, Noray.local_port)
if err != OK:
return err
return OK
```

> *Note:* Make sure to **always** specifiy the local port for the client - this
> is the only port noray recognizes, and failing to specify it will result in
> broken connectivity.
Host example:

```gdscript
func _ready():
Noray.on_connect_nat.connect(_handle_connect)
Noray.on_connect_relay.connect(_handle_connect)
func _handle_connect(address: String, port: int) -> Error:
var peer = get_tree().get_multiplayer().multiplayer_peer as ENetMultiplayerPeer
var err = await PacketHandshake.over_enet(peer.host, address, port)
if err != OK:
return err
return OK
```

> *Note:* The host handshake is a bit different, as it can't receive manual
> packets, only send them. So it assumes that the target is always responsive,
> and just blasts them with a bunch of packets. If the target is indeed
> responsive, it can connect. If not, nothing happens, as expected.
[noray]: https://github.com/foxssake/noray
[NAT Punch-through for Multiplayer Games]: https://keithjohnston.wordpress.com/2014/02/17/nat-punch-through-for-multiplayer-games/
[PacketHandshake]: ./packet-handshake.md
Loading

0 comments on commit 1b5205d

Please sign in to comment.