From d011885c878e8a6286b6793b33148ca39f76e04b Mon Sep 17 00:00:00 2001 From: wan-may Date: Tue, 26 Sep 2023 00:05:54 -0300 Subject: [PATCH] Accommodate IPv6 addresses at packet level. Avoid infinite loops in deserialisation. Test with timing loop removed. --- src/client/assets/strings/english.lua | 1 + src/client/game.lua | 31 +++++++++-- src/client/udp.lua | 7 ++- src/client/ui/browser.lua | 2 + src/metaserver/conf.lua | 16 +++--- src/metaserver/main.lua | 5 +- src/server/conf.lua | 2 +- src/server/main.lua | 75 ++++++++++++++++++--------- src/shared/hash.lua | 2 +- src/shared/ipstring.lua | 19 +++---- src/shared/packet.lua | 12 ++--- 11 files changed, 108 insertions(+), 64 deletions(-) diff --git a/src/client/assets/strings/english.lua b/src/client/assets/strings/english.lua index c4440ff..85a2751 100644 --- a/src/client/assets/strings/english.lua +++ b/src/client/assets/strings/english.lua @@ -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 } ) \ No newline at end of file diff --git a/src/client/game.lua b/src/client/game.lua index c282e60..30bdaa8 100644 --- a/src/client/game.lua +++ b/src/client/game.lua @@ -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 diff --git a/src/client/udp.lua b/src/client/udp.lua index c5e7eba..20417a6 100644 --- a/src/client/udp.lua +++ b/src/client/udp.lua @@ -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 diff --git a/src/client/ui/browser.lua b/src/client/ui/browser.lua index 525f9d6..23b1961 100644 --- a/src/client/ui/browser.lua +++ b/src/client/ui/browser.lua @@ -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() diff --git a/src/metaserver/conf.lua b/src/metaserver/conf.lua index 4acdd9d..bbbf1b6 100644 --- a/src/metaserver/conf.lua +++ b/src/metaserver/conf.lua @@ -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 \ No newline at end of file diff --git a/src/metaserver/main.lua b/src/metaserver/main.lua index cb723d9..acc4c3f 100644 --- a/src/metaserver/main.lua +++ b/src/metaserver/main.lua @@ -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 ) diff --git a/src/server/conf.lua b/src/server/conf.lua index 35ab969..c69eda2 100644 --- a/src/server/conf.lua +++ b/src/server/conf.lua @@ -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 \ No newline at end of file diff --git a/src/server/main.lua b/src/server/main.lua index 6c77061..379cfe2 100644 --- a/src/server/main.lua +++ b/src/server/main.lua @@ -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 diff --git a/src/shared/hash.lua b/src/shared/hash.lua index 88c55be..9198c36 100644 --- a/src/shared/hash.lua +++ b/src/shared/hash.lua @@ -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 { diff --git a/src/shared/ipstring.lua b/src/shared/ipstring.lua index f775402..96654cb 100644 --- a/src/shared/ipstring.lua +++ b/src/shared/ipstring.lua @@ -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 \ No newline at end of file diff --git a/src/shared/packet.lua b/src/shared/packet.lua index e78d9bb..8c707b0 100644 --- a/src/shared/packet.lua +++ b/src/shared/packet.lua @@ -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{