first
This commit is contained in:
23
addons/netfox.noray/CONTRIBUTORS.md
Normal file
23
addons/netfox.noray/CONTRIBUTORS.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Contributors
|
||||
|
||||
This addon, and the entirety of [netfox] is a shared effort of [Fox's Sake
|
||||
Studio], and the community. The following is the list of community contributors
|
||||
involved with netfox:
|
||||
|
||||
* Alberto Klocker <albertok@gmail.com>
|
||||
* Bryan Lee <42545742+bryanmylee@users.noreply.github.com>
|
||||
* Dustie <77035922+DustieDog@users.noreply.github.com>
|
||||
* Jake Cattrall <krazyjakee@gmail.com>
|
||||
* Jon Stevens <1030863+jonstvns@users.noreply.github.com>
|
||||
* Joseph Michael Ware <9at25jnr3@mozmail.com>
|
||||
* Nicolas Batty <nicolas.batty@gmail.com>
|
||||
* Riordan-DC <44295008+Riordan-DC@users.noreply.github.com>
|
||||
* Ryan Roden-Corrent <github@rcorre.net>
|
||||
* TheYellowArchitect <dmalandris@uth.gr>
|
||||
* TheYellowArchitect <hello@theyellowarchitect.com>
|
||||
* gk98s <89647115+gk98s@users.noreply.github.com>
|
||||
* zibetnu <9at25jnr3@mozmail.com>
|
||||
|
||||
[netfox]: https://github.com/foxssake/netfox
|
||||
[Fox's Sake Studio]: https://github.com/foxssake/
|
||||
|
||||
18
addons/netfox.noray/LICENSE
Normal file
18
addons/netfox.noray/LICENSE
Normal file
@@ -0,0 +1,18 @@
|
||||
Copyright 2023 Gálffy Tamás
|
||||
|
||||
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.
|
||||
39
addons/netfox.noray/README.md
Normal file
39
addons/netfox.noray/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# netfox.noray
|
||||
|
||||
Bulletproof your connectivity with [netfox]'s [noray] integration!
|
||||
|
||||
## Features
|
||||
|
||||
* 🤝 Establish connectivity using NAT punchthrough
|
||||
* Uses [noray] for orchestration
|
||||
* Implements a full UDP handshake
|
||||
* 🛜 Use [noray] as a relay
|
||||
* Useful in cases where NAT punchthrough fails
|
||||
* If you can see this repo, you probably can connect through [noray]
|
||||
|
||||
## Install
|
||||
|
||||
See the root [README](../../README.md).
|
||||
|
||||
> *Note*, that while *netfox.noray* is part of the *netfox* suite, it can be
|
||||
> used alone, without installing *netfox* itself.
|
||||
|
||||
## Usage
|
||||
|
||||
See the [docs](https://foxssake.github.io/netfox/netfox.noray/guides/noray/).
|
||||
|
||||
For a full example, see [noray-bootstrapper.gd].
|
||||
|
||||
## License
|
||||
|
||||
netfox.noray is under the [MIT license](LICENSE).
|
||||
|
||||
## Issues
|
||||
|
||||
In case of any issues, comments, or questions, please feel free to [open an issue]!
|
||||
|
||||
[netfox]: https://github.com/foxssake/netfox
|
||||
[source]: https://github.com/foxssake/netfox/archive/refs/heads/main.zip
|
||||
[noray]: https://github.com/foxssake/noray
|
||||
[noray-bootstrapper.gd]: ../../examples/shared/scripts/noray-bootstrapper.gd
|
||||
[open an issue]: https://github.com/foxssake/netfox/issues
|
||||
53
addons/netfox.noray/netfox-noray.gd
Normal file
53
addons/netfox.noray/netfox-noray.gd
Normal file
@@ -0,0 +1,53 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const ROOT = "res://addons/netfox.noray"
|
||||
|
||||
var SETTINGS = [
|
||||
_NetfoxLogger.make_setting("netfox/logging/netfox_noray_log_level")
|
||||
]
|
||||
|
||||
const AUTOLOADS = [
|
||||
{
|
||||
"name": "Noray",
|
||||
"path": ROOT + "/noray.gd"
|
||||
},
|
||||
{
|
||||
"name": "PacketHandshake",
|
||||
"path": ROOT + "/packet-handshake.gd"
|
||||
}
|
||||
]
|
||||
|
||||
func _enter_tree():
|
||||
for setting in SETTINGS:
|
||||
add_setting(setting)
|
||||
|
||||
for autoload in AUTOLOADS:
|
||||
add_autoload_singleton(autoload.name, autoload.path)
|
||||
|
||||
func _exit_tree():
|
||||
if ProjectSettings.get_setting("netfox/general/clear_settings", false):
|
||||
for setting in SETTINGS:
|
||||
remove_setting(setting)
|
||||
|
||||
for autoload in AUTOLOADS:
|
||||
remove_autoload_singleton(autoload.name)
|
||||
|
||||
func add_setting(setting: Dictionary):
|
||||
if ProjectSettings.has_setting(setting.name):
|
||||
return
|
||||
|
||||
ProjectSettings.set_setting(setting.name, setting.value)
|
||||
ProjectSettings.set_initial_value(setting.name, setting.value)
|
||||
ProjectSettings.add_property_info({
|
||||
"name": setting.get("name"),
|
||||
"type": setting.get("type"),
|
||||
"hint": setting.get("hint", PROPERTY_HINT_NONE),
|
||||
"hint_string": setting.get("hint_string", "")
|
||||
})
|
||||
|
||||
func remove_setting(setting: Dictionary):
|
||||
if not ProjectSettings.has_setting(setting.name):
|
||||
return
|
||||
|
||||
ProjectSettings.clear(setting.name)
|
||||
1
addons/netfox.noray/netfox-noray.gd.uid
Normal file
1
addons/netfox.noray/netfox-noray.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dj8vvyvvf4cl1
|
||||
207
addons/netfox.noray/noray.gd
Normal file
207
addons/netfox.noray/noray.gd
Normal file
@@ -0,0 +1,207 @@
|
||||
extends Node
|
||||
## A noray client for Godot.
|
||||
##
|
||||
## See: https://github.com/foxssake/noray
|
||||
|
||||
var _peer: StreamPeerTCP = StreamPeerTCP.new()
|
||||
var _protocol: NorayProtocolHandler = NorayProtocolHandler.new()
|
||||
var _address: String = ""
|
||||
var _oid: String = ""
|
||||
var _pid: String = ""
|
||||
var _local_port: int = -1
|
||||
|
||||
static var _logger: _NetfoxLogger = _NetfoxLogger.for_noray("Noray")
|
||||
|
||||
## Open ID.
|
||||
##
|
||||
## [i]read-only[/i], this is set after registering as host.
|
||||
var oid: String:
|
||||
get: return _oid
|
||||
set(v): push_error("Trying to set read-only variable oid")
|
||||
|
||||
## Private ID.
|
||||
##
|
||||
## [i]read-only[/i], this is set after registering as host.
|
||||
var pid: String:
|
||||
get: return _pid
|
||||
set(v): push_error("Trying to set read-only variable pid")
|
||||
|
||||
## Registered local port.
|
||||
##
|
||||
## This is the port that servers should listen on and the port that clients
|
||||
## should bind to ( i.e. use as local port ), since this port has been
|
||||
## registered with noray as part of this machine's external address, and this
|
||||
## is the port over which any handshake happens.
|
||||
##
|
||||
## [i]read-only[/i], this is set after registering remote.
|
||||
var local_port: int:
|
||||
get: return _local_port
|
||||
set(v): push_error("Trying to set read-only variable local_port")
|
||||
|
||||
## Emitted for any command received from noray.
|
||||
signal on_command(command: String, data: String)
|
||||
|
||||
## Emitted when connected to noray.
|
||||
signal on_connect_to_host()
|
||||
|
||||
## Emitted when disconnected from noray.
|
||||
signal on_disconnect_from_host()
|
||||
|
||||
## Emitted when an OpenID is received from noray.
|
||||
signal on_oid(oid: String)
|
||||
|
||||
## Emitted when a PrivateID is received from noray.
|
||||
signal on_pid(pid: String)
|
||||
|
||||
## Emitted when a connect over NAT command is received from noray.
|
||||
signal on_connect_nat(address: String, port: int)
|
||||
|
||||
## Emitted when a connect over relay command is received from noray.
|
||||
signal on_connect_relay(address: String, port: int)
|
||||
|
||||
func _enter_tree():
|
||||
_protocol.on_command.connect(func (cmd, data): on_command.emit(cmd, data))
|
||||
on_command.connect(_handle_commands)
|
||||
|
||||
## Connect to noray at host.
|
||||
func connect_to_host(hostname: String, port: int = 8890) -> Error:
|
||||
if is_connected_to_host():
|
||||
disconnect_from_host()
|
||||
|
||||
_logger.info("Trying to connect to noray at %s:%s", [hostname, port])
|
||||
|
||||
var address = IP.resolve_hostname(hostname, IP.TYPE_IPV4)
|
||||
if address:
|
||||
_logger.debug("Resolved noray host to %s", [address])
|
||||
else:
|
||||
_logger.error("Couldn't resolve hostname %s", [hostname])
|
||||
|
||||
var err = _peer.connect_to_host(address, port)
|
||||
if err != Error.OK:
|
||||
return err
|
||||
|
||||
_peer.set_no_delay(true)
|
||||
_protocol.reset()
|
||||
|
||||
while _peer.get_status() < 2:
|
||||
_peer.poll()
|
||||
await get_tree().process_frame
|
||||
|
||||
if _peer.get_status() == _peer.STATUS_CONNECTED:
|
||||
_address = address
|
||||
_logger.info("Connected to noray at %s:%s", [address, port])
|
||||
on_connect_to_host.emit()
|
||||
return OK
|
||||
else:
|
||||
_logger.error("Connection failed to noray at %s:%s, connection status %s", [address, port, _peer.get_status()])
|
||||
disconnect_from_host()
|
||||
return ERR_CONNECTION_ERROR
|
||||
|
||||
## Check if connected to any host.
|
||||
func is_connected_to_host() -> bool:
|
||||
return _peer.get_status() == _peer.STATUS_CONNECTED
|
||||
|
||||
## Disconnect from noray.
|
||||
##
|
||||
## Does nothing if already disconnected.
|
||||
func disconnect_from_host():
|
||||
if is_connected_to_host():
|
||||
on_disconnect_from_host.emit()
|
||||
_peer.disconnect_from_host()
|
||||
|
||||
## Register as host.
|
||||
func register_host() -> Error:
|
||||
return _put_command("register-host")
|
||||
|
||||
## Register remote address.
|
||||
func register_remote(registrar_port: int = 8809, timeout: float = 8, interval: float = 0.1) -> Error:
|
||||
if not is_connected_to_host():
|
||||
return ERR_CONNECTION_ERROR
|
||||
|
||||
if not pid:
|
||||
return ERR_UNAUTHORIZED
|
||||
|
||||
var result = ERR_TIMEOUT
|
||||
var udp = PacketPeerUDP.new()
|
||||
udp.bind(0)
|
||||
udp.set_dest_address(_address, registrar_port)
|
||||
|
||||
_logger.debug("Bound UDP to port %s", [udp.get_local_port()])
|
||||
|
||||
var packet = pid.to_utf8_buffer()
|
||||
|
||||
while timeout > 0:
|
||||
udp.put_packet(packet)
|
||||
|
||||
while udp.get_available_packet_count() > 0:
|
||||
var recv = udp.get_packet().get_string_from_utf8()
|
||||
if recv == "OK":
|
||||
_local_port = udp.get_local_port()
|
||||
_logger.info("Registered local port %s to remote", [_local_port])
|
||||
result = OK
|
||||
timeout = 0 # Break outer loop
|
||||
break
|
||||
else:
|
||||
_logger.error("Failed to register local port!")
|
||||
result = FAILED
|
||||
timeout = 0 # Break outer loop
|
||||
break
|
||||
# Sleep
|
||||
await get_tree().create_timer(interval).timeout
|
||||
timeout -= interval
|
||||
|
||||
udp.close()
|
||||
return result
|
||||
|
||||
## Connect to a given host by OID over NAT.
|
||||
func connect_nat(host_oid: String) -> Error:
|
||||
return _put_command("connect", host_oid)
|
||||
|
||||
## Connect to a given host by OID over relay.
|
||||
func connect_relay(host_oid: String) -> Error:
|
||||
return _put_command("connect-relay", host_oid)
|
||||
|
||||
func _process(_delta):
|
||||
if not is_connected_to_host():
|
||||
return
|
||||
|
||||
_peer.poll()
|
||||
var available = _peer.get_available_bytes()
|
||||
if available <= 0:
|
||||
return
|
||||
|
||||
_protocol.ingest(_peer.get_utf8_string(available))
|
||||
|
||||
func _put_command(command: String, data = null) -> Error:
|
||||
if not is_connected_to_host():
|
||||
return ERR_CONNECTION_ERROR
|
||||
|
||||
if data != null:
|
||||
_peer.put_data(("%s %s\n" % [command, data]).to_utf8_buffer())
|
||||
else:
|
||||
_peer.put_data((command + "\n").to_utf8_buffer())
|
||||
|
||||
return OK
|
||||
|
||||
func _handle_commands(command: String, data: String):
|
||||
if command == "set-oid":
|
||||
_oid = data
|
||||
on_oid.emit(oid)
|
||||
_logger.debug("Saved OID: %s", [oid])
|
||||
elif command == "set-pid":
|
||||
_pid = data
|
||||
on_pid.emit(pid)
|
||||
_logger.debug("Saved PID: %s", [pid])
|
||||
elif command == "connect":
|
||||
var parts = data.split(":")
|
||||
var host = parts[0]
|
||||
var port = parts[1].to_int()
|
||||
_logger.debug("Received connect command to %s:%s", [host, port])
|
||||
on_connect_nat.emit(host, port)
|
||||
elif command == "connect-relay":
|
||||
var host = _address
|
||||
var port = data.to_int()
|
||||
_logger.debug("Received connect relay command to %s:%s", [host, port])
|
||||
on_connect_relay.emit(host, port)
|
||||
else:
|
||||
_logger.trace("Received command %s %s", [command, data])
|
||||
1
addons/netfox.noray/noray.gd.uid
Normal file
1
addons/netfox.noray/noray.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://gu4bv4tgms3x
|
||||
83
addons/netfox.noray/packet-handshake.gd
Normal file
83
addons/netfox.noray/packet-handshake.gd
Normal file
@@ -0,0 +1,83 @@
|
||||
extends Node
|
||||
## This class implements a handshake protocol over UDP for multiple classes.
|
||||
|
||||
class HandshakeStatus:
|
||||
var did_read: bool = false
|
||||
var did_write: bool = false
|
||||
var did_handshake: bool = false
|
||||
|
||||
func _to_string():
|
||||
return "$" + \
|
||||
("r" if did_read else "-") + \
|
||||
("w" if did_write else "-") + \
|
||||
("x" if did_handshake else "-")
|
||||
|
||||
static func from_string(str: String) -> HandshakeStatus:
|
||||
var result = HandshakeStatus.new()
|
||||
result.did_read = str.contains("r")
|
||||
result.did_write = str.contains("w")
|
||||
result.did_handshake = str.contains("x")
|
||||
return result
|
||||
|
||||
## Conduct handshake over a [PacketPeer] instance.
|
||||
func over_packet_peer(peer: PacketPeer, timeout: float = 8.0, frequency: float = 0.1) -> Error:
|
||||
var result = ERR_TIMEOUT
|
||||
var status = HandshakeStatus.new()
|
||||
status.did_write = true
|
||||
|
||||
while timeout >= 0:
|
||||
# Process incoming packets
|
||||
while peer.get_available_packet_count() > 0:
|
||||
var packet = peer.get_packet()
|
||||
var incoming_status = HandshakeStatus.from_string(packet.get_string_from_ascii())
|
||||
|
||||
# We did get a packet, so that means we read them
|
||||
status.did_read = true
|
||||
|
||||
# They already read us, so that's a handshake
|
||||
if incoming_status.did_read:
|
||||
status.did_handshake = true
|
||||
|
||||
# Both peers ack'd the handshake, we've succeeded
|
||||
if incoming_status.did_handshake and status.did_handshake:
|
||||
result = OK
|
||||
timeout = 0 # Break outer loop
|
||||
|
||||
# Send our state
|
||||
peer.put_packet(status.to_string().to_ascii_buffer())
|
||||
|
||||
await get_tree().create_timer(frequency).timeout
|
||||
timeout -= frequency
|
||||
|
||||
# If we've read them and we know we've sent data to them successfully,
|
||||
# we're *probably* good to connect, even if the handshake did not actually
|
||||
# go through.
|
||||
# Depending on the context, the calling code may decide to connect anyway,
|
||||
# based on this return value.
|
||||
if status.did_read and status.did_write and not status.did_handshake:
|
||||
result = ERR_BUSY
|
||||
|
||||
return result
|
||||
|
||||
## Conduct handshake over an [ENetConnection].
|
||||
##
|
||||
## [i]Note[/i] that this is not a full-fledged handshake, since we can't receive
|
||||
## data over the connection. Instead, we just pretend that the handshake is
|
||||
## successful on our end and blast that status for a given time.
|
||||
func over_enet(connection: ENetConnection, address: String, port: int, timeout: float = 8.0, frequency: float = 0.1) -> Error:
|
||||
var result = OK
|
||||
var status = HandshakeStatus.new()
|
||||
|
||||
# Pretend this is a perfectly healthy handshake, since we can't receive data here
|
||||
status.did_write = true
|
||||
status.did_read = true
|
||||
status.did_handshake = true
|
||||
|
||||
while timeout >= 0:
|
||||
# Send our state
|
||||
connection.socket_send(address, port, status.to_string().to_ascii_buffer())
|
||||
|
||||
await get_tree().create_timer(frequency).timeout
|
||||
timeout -= frequency
|
||||
|
||||
return result
|
||||
1
addons/netfox.noray/packet-handshake.gd.uid
Normal file
1
addons/netfox.noray/packet-handshake.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://tt3j25a05n7c
|
||||
7
addons/netfox.noray/plugin.cfg
Normal file
7
addons/netfox.noray/plugin.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="netfox.noray"
|
||||
description="Bulletproof your connectivity with noray integration for netfox"
|
||||
author="Tamas Galffy and contributors"
|
||||
version="1.25.3"
|
||||
script="netfox-noray.gd"
|
||||
34
addons/netfox.noray/protocol-handler.gd
Normal file
34
addons/netfox.noray/protocol-handler.gd
Normal file
@@ -0,0 +1,34 @@
|
||||
extends RefCounted
|
||||
class_name NorayProtocolHandler
|
||||
## This class parses incoming data from noray's protocol.
|
||||
##
|
||||
## Unless you're writing your own noray integration, [Noray] should cover most
|
||||
## use cases.
|
||||
|
||||
## Emitted for every command parsed during a [method ingest] call.
|
||||
signal on_command(command: String, data: String)
|
||||
|
||||
var _strbuf: String = ""
|
||||
|
||||
## Resets the parser.
|
||||
func reset():
|
||||
_strbuf = ""
|
||||
|
||||
## Parse an incoming piece of data.
|
||||
func ingest(data: String):
|
||||
_strbuf += data
|
||||
if not _strbuf.contains("\n"):
|
||||
return
|
||||
|
||||
var idx = _strbuf.rfind("\n")
|
||||
var lines = _strbuf.substr(0, idx).split("\n", false)
|
||||
_strbuf = _strbuf.erase(0, idx + 1)
|
||||
|
||||
for line in lines:
|
||||
if not line.contains(" "):
|
||||
on_command.emit(line, "")
|
||||
else:
|
||||
var parts = line.split(" ")
|
||||
var command = parts[0]
|
||||
var param = parts[1]
|
||||
on_command.emit(command, param)
|
||||
1
addons/netfox.noray/protocol-handler.gd.uid
Normal file
1
addons/netfox.noray/protocol-handler.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ou0pi8ebvqsi
|
||||
Reference in New Issue
Block a user