Accommodate IPv6 addresses at packet level. Avoid infinite loops in deserialisation. Test with timing loop removed.

This commit is contained in:
wan-may 2023-09-26 00:05:54 -03:00
parent 74e328d86c
commit d011885c87
11 changed files with 108 additions and 64 deletions

View File

@ -19,4 +19,5 @@ return setmetatable({
["svinfo_port"] = "Port",
["refresh_button"] = "Refresh",
["cancel_button"] = "Cancel",
game_intro = "A gaze blank and pitiless as the sun",
}, {__index = function( t, k ) return k end } )

View File

@ -6,22 +6,44 @@ local packet = shared.packet
local crepuscular = assert( require 'crepuscular' )
local game = {}
local handlers = setmetatable( {}, {__index = function() return print end } )
local t = 0
local tick = 0
local serverTick = "0"
local serverTick = 0
function handlers.connected( data )
serverTick = math.max( data.tick, serverTick )
end
function handlers.insect( data )
end
function handlers.soleil( data )
end
function handlers.playerChange( data )
end
function handlers.chatMessage( msg )
print( msg.cmsg )
end
function game.draw()
lg.setColor( 1, 1, 1, 1 )
lg.print( tick, 0, 0 )
lg.print( serverTick, 0, 25 )
end
function game.onPacket( msg )
if not msg then return end
if not msg or (#msg < 1) then return end
local msgs, types = packet.deserialise( msg )
if not msgs then return game.onPacket( server.receive() ) end
for i = 1, #msgs do
game[ types[i] ]( msgs[i], ip, port )
--Handler returns something if msg should be discarded.
if handlers[ types[i] ]( msgs[i] ) then break end
end
return game.onPacket( server.receive() )
end
@ -32,7 +54,8 @@ function game.update( dt )
if t > 0.1 then
t = 0
tick = tick + 1
assert( server.send( packet.get( packet.heartbeat{ tick = tick, hash = 1234 } ) ) )
server.newPacket()
assert( server.send( packet.get() ) )
end
end

View File

@ -31,6 +31,11 @@ function udp.requestServerList()
return mscxn:send( packet.get() )
end
function udp.newPacket( tick )
packet.get()
if token then packet.connected{ token = token, tick = tick or 0 } end
end
function udp.setToken( token )
token = token
end
@ -51,6 +56,7 @@ end
function udp.connect( ip, port )
assert( cxn:setpeername( ip, port ) )
print( "Connection request to:", ip, port )
return udp.send( packet.get( packet.clientInfo{ username = config.plName } ) )
end
@ -61,7 +67,6 @@ function udp.disconnect( )
end
function udp.send( s )
print( "udp out:", s )
return cxn:send( s )
end

View File

@ -210,6 +210,8 @@ end
function browser.joinIPString( s )
--Parse IP address and port from string. If it's valid, join the server.
--TODO: there should be two fields, one for IP, one for port.
--Parsing the entered address for the port is possible but more error-prone.
print( "browser: entered IP and port", s )
if not s then return end
ti:clear()

View File

@ -34,18 +34,18 @@ function love.conf(t)
t.modules.data = true -- Enable the data module (boolean)
t.modules.event = true -- Enable the event module (boolean)
t.modules.font = false -- Enable the font module (boolean)
t.modules.graphics = true -- Enable the graphics module (boolean)
t.modules.image = true -- Enable the image module (boolean)
t.modules.graphics = false -- Enable the graphics module (boolean)
t.modules.image = false -- Enable the image module (boolean)
t.modules.joystick = false -- Enable the joystick module (boolean)
t.modules.keyboard = true -- Enable the keyboard module (boolean)
t.modules.math = true -- Enable the math module (boolean)
t.modules.mouse = true -- Enable the mouse module (boolean)
t.modules.keyboard = false -- Enable the keyboard module (boolean)
t.modules.math = false -- Enable the math module (boolean)
t.modules.mouse = false -- Enable the mouse module (boolean)
t.modules.physics = false -- Enable the physics module (boolean)
t.modules.sound = false -- Enable the sound module (boolean)
t.modules.system = true -- Enable the system module (boolean)
t.modules.thread = true -- Enable the thread module (boolean)
t.modules.system = false -- Enable the system module (boolean)
t.modules.thread = false -- Enable the thread module (boolean)
t.modules.timer = true -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update
t.modules.touch = false -- Enable the touch module (boolean)
t.modules.video = false -- Enable the video module (boolean)
t.modules.window = true -- Enable the window module (boolean)
t.modules.window = false -- Enable the window module (boolean)
end

View File

@ -29,7 +29,7 @@ local handlers = setmetatable({
--NAT punch: the server doesn't know its own external IP
--so it contacts a third party (the metaserver) to discover it.
--This external IP gets advertised to prospective clients.
svInfo.ip = shared.ip.fromString( ip )
svInfo.ip.ip = ip
svInfo.port = port
end
servers[ip..port].time = t
@ -56,8 +56,7 @@ local handlers = setmetatable({
local t = socket.gettime()
clients[ip].time = t
packet.heartbeat{ tick = tick }
for svIP, server in pairs( servers ) do
print( "", svIP, packet.getString( server.info.svname ))
packet.serverInfo( server.info )

View File

@ -47,5 +47,5 @@ function love.conf(t)
t.modules.timer = true -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update
t.modules.touch = false -- Enable the touch module (boolean)
t.modules.video = false -- Enable the video module (boolean)
t.modules.window = true -- Enable the window module (boolean)
t.modules.window = false -- Enable the window module (boolean)
end

View File

@ -24,27 +24,48 @@ local server = { tick = 0, }
local handlers = setmetatable({
connected = function( msg, ip, port )
local client = clients[msg.token]
if not client or
(client.ip ~= ip) or
(client.port ~= port)
then
print( "Invalid token from IP:", msg.token, ip, port )
return true
end
if client.tick > msg.tick then
print( "Old packet received:", msg.tick, ip )
return true
end
client.tick = msg.tick
end,
clChimo = function( clChimo, ip, port )
if not connecting[ ip..port ] then
return print( "Old connection attempt from:", ip, port )
local key = ip..":"..port
if not connecting[ key ] then
print( "Old connection attempt from:", key )
return true
end
local remoteHash = clChimo.hash
local clNonce = clChimo.nonce
local svNonce = connecting[ip..port].nonce
local localHash = shared.hash.hash( clNonce, svNonce )
if localHash ~= remoteHash then
return print( "Hashes differ:", shared.hash.hex( clNonce ), shared.hash.hex( svNonce ) )
local svNonce = connecting[key].nonce
local token = shared.hash.hash( clNonce, svNonce )
if token ~= remoteHash then
print( "Hashes differ:", shared.hash.hex( clNonce ), shared.hash.hex( svNonce ) )
return true
end
print( "Client connected:", ip, port, localHash )
clients[ localHash ] = { ip = ip, port = port }
packet.connected{ token = localHash }
print( "Client connected:", ip, port, token )
clients[ token ] = connecting[ key ]
packet.connected{ token = token, tick = server.tick }
return udp:sendto( packet.get(), ip, port )
end,
advertised = function( ack, ip, port )
if ip ~= shared.metaserver.ip then return print( "Advertisement acked from rogue address:", ip, port ) end
if udp:getsockname() then
assert( udp:getsockname() == tostring( ack.ip ) )
--assert( udp:getsockname() == tostring( ack.ip ), print( tostring( ack.ip ), udp:getsockname()) or "IP mismatch!" )
return print( "Advertised. Address already set." )
end
print( "Advertised. Setting address:", ack.ip, ack.port )
@ -53,8 +74,8 @@ local handlers = setmetatable({
clientInfo = function( clientInfo, ip, port )
local key = ip..port
connecting[key] = connecting[key] or {}
local key = ip..":"..port
connecting[key] = connecting[key] or { ip = ip, port = port, tick = 0 }
local client = connecting[key]
local nonce = shared.hash.rand()
client.nonce = nonce
@ -85,17 +106,16 @@ end
--Incoming packet.
function server.Parse( msg, ip, port )
if not msg then return end
if not clients[ip] then
print( "New IP:", ip, port )
clients[ip] = { ip = ip, port = port }
end
if (not msg) or (#msg < 1) then return end
print( "Parsing:", ip, port, #msg, msg )
local msgs, types = packet.deserialise( msg )
if msgs then for i = 1, #msgs do
print( "Received: ", types[i], ip, port )
handlers[ types[i] ]( msgs[i], ip, port )
end end
if msgs then
for i = 1, #msgs do
print( "Received: ", types[i], ip, port )
if handlers[ types[i] ]( msgs[i], ip, port ) then break end
end
else print( types )
end
return server.Parse( udp:receivefrom() ) -- Process other packets.
end
@ -124,8 +144,13 @@ end
function server.Advance()
server.tick = server.tick + 1
for id, client in pairs( clients ) do
--
if server.tick % 100 == 0 then
for id, client in pairs( clients ) do
packet.get()
print( "updating client:", id )
packet.connected{ token = id, tick = server.tick }
udp:sendto( packet.get(), client.ip, client.port )
end
end
end
@ -142,7 +167,7 @@ server.Start()
function love.update( dt )
server.Parse( udp:receivefrom() )
server.Advance()
if server.tick % 250 == 0 then
if server.tick % 4000 == 0 then
server.Advertise()
server.Parse( mscxn:receive(), shared.metaserver.ip, shared.metaserver.port )
end

View File

@ -1,6 +1,6 @@
local math = math
local bit = assert( require 'bit' )
local max = bit.tobit( 0xffffffff )
local max = math.huge
math.randomseed( 4 )
--hash of a pair of 32-bit numbers
return {

View File

@ -1,30 +1,23 @@
--CData structure that can hold the longest string representation of an IPv6 address
--we do this because LuaSocket expects a Lua string, and because we can't guarantee v4-only addresses
local ffi = assert( require 'ffi' )
local ipString = {}
local string = assert( string )
ffi.cdef[[
typedef struct {
uint8_t a;
uint8_t b;
uint8_t c;
uint8_t d;
char ip[40];
} ipAddress;
]]
local ipAddress = ffi.typeof( ffi.new( "ipAddress" ) )
ffi.metatype( ipAddress, { __tostring = function( ip ) return ip.a.."."..ip.b.."."..ip.c.."."..ip.d end } )
ffi.metatype( ipAddress, { __tostring = function( ip ) return ffi.string( ip.ip, ffi.sizeof( ip.ip ) ) end } )
function ipString.new( t )
local ip = ffi.new( ipAddress )
ip.a, ip.b, ip.c, ip.d = t[1], t[2], t[3], t[4]
ip.ip = t
return ip
end
function ipString.fromString( s )
local t = {}
for octet in s:gmatch( '%d+' ) do
table.insert( t, tonumber( octet ) )
end
return ipString.new( t )
end
ipString.fromString = ipString.new
return ipString

View File

@ -51,6 +51,7 @@ newStruct{
newStruct{
name = "connected",
"uint32_t token",
"uint32_t tick",
}
newStruct{
@ -58,12 +59,6 @@ newStruct{
"char padding[300]" --Just a bunch of padding to mitigate amplification.
}
newStruct{
name = "heartbeat",
"uint32_t tick",
"uint32_t hash",
}
newStruct{
name = "insect",
"uint8_t id",
@ -93,6 +88,7 @@ newStruct{
newStruct{
name = "chatMessage",
"uint8_t id",
"char cmsg[127]",
}
@ -110,7 +106,7 @@ newStruct{
newStruct{
name = "disconnect",
"uint32_t reason",
"char reason",
}
local readBuffer = buffer.new( 1024 )
@ -175,7 +171,7 @@ if testing then
svname = "😘😘😘😘😘😘😘kissyfaceserver",
version = 25,
port = 51312,
ip = ipString.new{ 132, 213, 45, 21 }
ip = ipString.new '132.145.25.62'
}
packet.heartbeat{