hamncheese/hamncheese/Scripts/peers.gd
2023-09-29 19:18:57 -05:00

257 lines
7.5 KiB
GDScript

#
# Ham'n'Cheese
# Copyright (C) 2023-2024 Scott Duensing <scott@kangaroopunch.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>
#
extends Node
signal peer_received_packet(peer, verb, payload)
var _tcp := TCPServer.new()
var _basePort : int
var _serverRunning: bool
var _timer : Timer
# This array is of dictionary elements that contain:
# "id": Unique ID of this peer (1-254) based on IP.
# "ip": IP address of this peer.
# "port": Listen port of this peer.
# "user": User name on this IP.
# "uuid": UUID for this peer.
var _userInfo: Dictionary
# This array is of dictionary elements that contain:
# "tcp": TCP connection to this peer.
# "id": Unique ID of this peer (1-254) based on IP.
# "ip": IP address of this peer.
# "online": Will be true when TCP network can talk to the peer.
# "port": Listen port of this peer.
# "user": User name on this IP.
# "uuid": UUID for this peer.
# "refresh": Do we need to redraw this entry?
#
# In addition, it contains all the properties of the C++ peer list:
# "_tag": Sequence number for communicating with the management API.
# "_type": Either "pSp" (via supernode) or "p2p" (direct) connection.
# "ip4addr": IP address of this peer with subnet in CIDR notation.
# "purgeable": Can this peer be removed from the list if needed?
# "local": (unknown)
# "macaddr": MAC address of the peer.
# "sockaddr": IP and socket of connected supernode.
# "desc": Description of this peer, usually the machine name.
# "last_p2p": Last P2P packet time.
# "last_sent_query": (unknown)
# "last_seen": Last time this peer was seen on the network.
var peerArray: Array
func _on_timer_timeout():
if _serverRunning:
# Tell everyone we're alive.
for peer in peerArray:
if peer["tcp"] != null:
peer["tcp"].poll()
if peer["tcp"].get_status() == StreamPeerTCP.STATUS_CONNECTED:
send_to_peer(peer, "status", { "online": true, "user": _userInfo["user"], "uuid": _userInfo["uuid"] })
func _process(_delta):
if _serverRunning:
_tcp_server()
func _sort_by_ip(a, b):
# Sort numerically.
if Util.ip_string_to_int(a["ip"]) < Util.ip_string_to_int(b["ip"]):
return true
return false
func _tcp_server():
if _tcp.is_listening():
# New inbound connection.
if _tcp.is_connection_available():
var new_peer: StreamPeerTCP = _tcp.take_connection()
# Do we have a connection for this peer already?
for peer in peerArray:
if peer["ip"] == new_peer.get_connected_host():
if peer["tcp"] == null:
# No - add it!
peer["tcp"] = new_peer
print("New peer ", peer["ip"], ":", peer["port"])
else:
# Yes. Close existing, use this one.
peer["tcp"].disconnect_from_host()
peer["tcp"] = new_peer
print("Updated peer ", peer["ip"], ":", peer["port"])
peer["tcp"].poll()
if peer["tcp"].get_status() == StreamPeerTCP.STATUS_CONNECTED:
peer["tcp"].set_no_delay(true) # ***TODO*** This throws an error some times.
# Process incoming data and attempt connections.
for peer in peerArray:
if peer["tcp"] == null:
# Try to connect.
print("Connecting to ", peer["ip"], ":", peer["port"])
peer["tcp"] = StreamPeerTCP.new()
var error = peer["tcp"].connect_to_host(peer["ip"], peer["port"])
if error == OK:
peer["tcp"].set_no_delay(true)
else:
peer["tcp"] = null
else:
# Update network status.
peer["tcp"].poll()
# Still connected?
if peer["tcp"].get_status() == StreamPeerTCP.STATUS_ERROR:
peer["tcp"].disconnect_from_host()
peer["tcp"] = null
print("Lost peer ", peer["ip"], ":", peer["port"])
else:
# Data waiting?
if peer["tcp"].get_status() == StreamPeerTCP.STATUS_CONNECTED:
if peer["tcp"].get_available_bytes() > 0:
var data = peer["tcp"].get_var()
peer_received_packet.emit(peer, data["verb"], data["payload"])
func clear():
peerArray.clear()
func disconnect_peer(peer):
if peer["tcp"] != null:
send_to_peer(peer, "status", { "online": false })
peer["tcp"].disconnect_from_host()
peer["tcp"] = null
print("Disconnected peer ", peer["ip"], ":", peer["port"])
func is_running():
return _serverRunning
func send_to_peer(peer, verb: String, payload: Dictionary):
if peer["tcp"] != null:
if peer["tcp"].get_status() == StreamPeerTCP.STATUS_CONNECTED:
var message = {}
message["verb"] = verb
message["payload"] = payload
peer["tcp"].put_var(message)
peer["tcp"].poll()
func start_server(address, portStart, user, uuid):
if !_serverRunning:
clear()
# We are creating the peer port number by combining the portStart with the
# last octet of the peer's VPN IP address. Since this octet cannot be zero,
# we actually use portStart-1 as the base so we don't waste the first port
# specified by portStart.
_basePort = portStart - 1
_userInfo["ip"] = address
_userInfo["user"] = user
_userInfo["uuid"] = uuid
_userInfo["id"] = _userInfo["ip"].get_slice(".", 3).to_int() # Use last octet for our ID.
_userInfo["port"] = _basePort + _userInfo["id"]
# Set up our listening port.
_tcp.listen(_userInfo["port"], _userInfo["ip"])
_serverRunning = true
func startup():
_serverRunning = false
clear()
_timer = Timer.new()
_timer.wait_time = 1
_timer.one_shot = false
_timer.timeout.connect(_on_timer_timeout)
Engine.get_main_loop().current_scene.add_child(_timer)
_timer.start()
func stop_server():
if _serverRunning:
# Tear down network.
_serverRunning = false
for peer in peerArray:
disconnect_peer(peer)
_tcp.stop()
func update(peersFromCPP: Array):
var found
var changed = false
var oldArray: Array = []
if !_serverRunning:
return
#print("\nPeers Raw: ", peersFromCPP)
#print("Peers Before: ", peerArray)
# Remember what we started with.
oldArray = peerArray.duplicate(true)
# Start a new peer list.
peerArray.clear()
# Add everyone connected.
for peerCPP in peersFromCPP:
if peerCPP["ip4addr"] == "":
continue
# Get an IP without netmask.
peerCPP["ip"] = peerCPP["ip4addr"].get_slice("/", 0)
# Do we have this peer's information from before?
found = false
for oldPeer in oldArray:
if oldPeer["ip"] == peerCPP["ip"]:
found = true
# Sometimes we get duplicates, prevent adding a peer twice.
for knownPeer in peerArray:
if knownPeer["ip"] == oldPeer["ip"]:
continue
peerCPP = oldPeer
if !found:
# We didn't have them. Add needed records.
peerCPP["user"] = ""
peerCPP["uuid"] = ""
peerCPP["online"] = false
peerCPP["refresh"] = false
changed = true
# Also create the TCP connection data.
peerCPP["id"] = peerCPP["ip"].get_slice(".", 3).to_int() # Use last octet for our ID.
peerCPP["port"] = _basePort + peerCPP["ip"].get_slice(".", 3).to_int()
peerCPP["tcp"] = null
# Add them to the new list.
peerArray.append(peerCPP)
# Check if we need to redraw anyone.
for peer in peerArray:
if peer["refresh"] == true:
peer["refresh"] = false
changed = true
# Sort new array.
peerArray.sort_custom(_sort_by_ip)
#print("Peers After: ", peerArray)
return changed