257 lines
7.5 KiB
GDScript
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
|