Accommodate IPv6 addresses at packet level. Avoid infinite loops in deserialisation. Test with timing loop removed.
This commit is contained in:
parent
74e328d86c
commit
d011885c87
|
@ -19,4 +19,5 @@ return setmetatable({
|
||||||
["svinfo_port"] = "Port",
|
["svinfo_port"] = "Port",
|
||||||
["refresh_button"] = "Refresh",
|
["refresh_button"] = "Refresh",
|
||||||
["cancel_button"] = "Cancel",
|
["cancel_button"] = "Cancel",
|
||||||
|
game_intro = "A gaze blank and pitiless as the sun",
|
||||||
}, {__index = function( t, k ) return k end } )
|
}, {__index = function( t, k ) return k end } )
|
|
@ -6,22 +6,44 @@ local packet = shared.packet
|
||||||
local crepuscular = assert( require 'crepuscular' )
|
local crepuscular = assert( require 'crepuscular' )
|
||||||
|
|
||||||
local game = {}
|
local game = {}
|
||||||
|
local handlers = setmetatable( {}, {__index = function() return print end } )
|
||||||
local t = 0
|
local t = 0
|
||||||
local tick = 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()
|
function game.draw()
|
||||||
|
lg.setColor( 1, 1, 1, 1 )
|
||||||
lg.print( tick, 0, 0 )
|
lg.print( tick, 0, 0 )
|
||||||
lg.print( serverTick, 0, 25 )
|
lg.print( serverTick, 0, 25 )
|
||||||
end
|
end
|
||||||
|
|
||||||
function game.onPacket( msg )
|
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 )
|
local msgs, types = packet.deserialise( msg )
|
||||||
if not msgs then return game.onPacket( server.receive() ) end
|
if not msgs then return game.onPacket( server.receive() ) end
|
||||||
for i = 1, #msgs do
|
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
|
end
|
||||||
return game.onPacket( server.receive() )
|
return game.onPacket( server.receive() )
|
||||||
end
|
end
|
||||||
|
@ -32,7 +54,8 @@ function game.update( dt )
|
||||||
if t > 0.1 then
|
if t > 0.1 then
|
||||||
t = 0
|
t = 0
|
||||||
tick = tick + 1
|
tick = tick + 1
|
||||||
assert( server.send( packet.get( packet.heartbeat{ tick = tick, hash = 1234 } ) ) )
|
server.newPacket()
|
||||||
|
assert( server.send( packet.get() ) )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,11 @@ function udp.requestServerList()
|
||||||
return mscxn:send( packet.get() )
|
return mscxn:send( packet.get() )
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function udp.newPacket( tick )
|
||||||
|
packet.get()
|
||||||
|
if token then packet.connected{ token = token, tick = tick or 0 } end
|
||||||
|
end
|
||||||
|
|
||||||
function udp.setToken( token )
|
function udp.setToken( token )
|
||||||
token = token
|
token = token
|
||||||
end
|
end
|
||||||
|
@ -51,6 +56,7 @@ end
|
||||||
|
|
||||||
function udp.connect( ip, port )
|
function udp.connect( ip, port )
|
||||||
assert( cxn:setpeername( ip, port ) )
|
assert( cxn:setpeername( ip, port ) )
|
||||||
|
print( "Connection request to:", ip, port )
|
||||||
return udp.send( packet.get( packet.clientInfo{ username = config.plName } ) )
|
return udp.send( packet.get( packet.clientInfo{ username = config.plName } ) )
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -61,7 +67,6 @@ function udp.disconnect( )
|
||||||
end
|
end
|
||||||
|
|
||||||
function udp.send( s )
|
function udp.send( s )
|
||||||
print( "udp out:", s )
|
|
||||||
return cxn:send( s )
|
return cxn:send( s )
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -210,6 +210,8 @@ end
|
||||||
|
|
||||||
function browser.joinIPString( s )
|
function browser.joinIPString( s )
|
||||||
--Parse IP address and port from string. If it's valid, join the server.
|
--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 )
|
print( "browser: entered IP and port", s )
|
||||||
if not s then return end
|
if not s then return end
|
||||||
ti:clear()
|
ti:clear()
|
||||||
|
|
|
@ -34,18 +34,18 @@ function love.conf(t)
|
||||||
t.modules.data = true -- Enable the data module (boolean)
|
t.modules.data = true -- Enable the data module (boolean)
|
||||||
t.modules.event = true -- Enable the event module (boolean)
|
t.modules.event = true -- Enable the event module (boolean)
|
||||||
t.modules.font = false -- Enable the font module (boolean)
|
t.modules.font = false -- Enable the font module (boolean)
|
||||||
t.modules.graphics = true -- Enable the graphics module (boolean)
|
t.modules.graphics = false -- Enable the graphics module (boolean)
|
||||||
t.modules.image = true -- Enable the image module (boolean)
|
t.modules.image = false -- Enable the image module (boolean)
|
||||||
t.modules.joystick = false -- Enable the joystick module (boolean)
|
t.modules.joystick = false -- Enable the joystick module (boolean)
|
||||||
t.modules.keyboard = true -- Enable the keyboard module (boolean)
|
t.modules.keyboard = false -- Enable the keyboard module (boolean)
|
||||||
t.modules.math = true -- Enable the math module (boolean)
|
t.modules.math = false -- Enable the math module (boolean)
|
||||||
t.modules.mouse = true -- Enable the mouse module (boolean)
|
t.modules.mouse = false -- Enable the mouse module (boolean)
|
||||||
t.modules.physics = false -- Enable the physics module (boolean)
|
t.modules.physics = false -- Enable the physics module (boolean)
|
||||||
t.modules.sound = false -- Enable the sound module (boolean)
|
t.modules.sound = false -- Enable the sound module (boolean)
|
||||||
t.modules.system = true -- Enable the system module (boolean)
|
t.modules.system = false -- Enable the system module (boolean)
|
||||||
t.modules.thread = true -- Enable the thread 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.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.touch = false -- Enable the touch module (boolean)
|
||||||
t.modules.video = false -- Enable the video 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
|
end
|
|
@ -29,7 +29,7 @@ local handlers = setmetatable({
|
||||||
--NAT punch: the server doesn't know its own external IP
|
--NAT punch: the server doesn't know its own external IP
|
||||||
--so it contacts a third party (the metaserver) to discover it.
|
--so it contacts a third party (the metaserver) to discover it.
|
||||||
--This external IP gets advertised to prospective clients.
|
--This external IP gets advertised to prospective clients.
|
||||||
svInfo.ip = shared.ip.fromString( ip )
|
svInfo.ip.ip = ip
|
||||||
svInfo.port = port
|
svInfo.port = port
|
||||||
end
|
end
|
||||||
servers[ip..port].time = t
|
servers[ip..port].time = t
|
||||||
|
@ -56,8 +56,7 @@ local handlers = setmetatable({
|
||||||
|
|
||||||
local t = socket.gettime()
|
local t = socket.gettime()
|
||||||
clients[ip].time = t
|
clients[ip].time = t
|
||||||
|
|
||||||
packet.heartbeat{ tick = tick }
|
|
||||||
for svIP, server in pairs( servers ) do
|
for svIP, server in pairs( servers ) do
|
||||||
print( "", svIP, packet.getString( server.info.svname ))
|
print( "", svIP, packet.getString( server.info.svname ))
|
||||||
packet.serverInfo( server.info )
|
packet.serverInfo( server.info )
|
||||||
|
|
|
@ -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.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.touch = false -- Enable the touch module (boolean)
|
||||||
t.modules.video = false -- Enable the video 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
|
end
|
|
@ -24,27 +24,48 @@ local server = { tick = 0, }
|
||||||
|
|
||||||
local handlers = setmetatable({
|
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 )
|
clChimo = function( clChimo, ip, port )
|
||||||
if not connecting[ ip..port ] then
|
local key = ip..":"..port
|
||||||
return print( "Old connection attempt from:", ip, port )
|
if not connecting[ key ] then
|
||||||
|
print( "Old connection attempt from:", key )
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
local remoteHash = clChimo.hash
|
local remoteHash = clChimo.hash
|
||||||
local clNonce = clChimo.nonce
|
local clNonce = clChimo.nonce
|
||||||
local svNonce = connecting[ip..port].nonce
|
local svNonce = connecting[key].nonce
|
||||||
local localHash = shared.hash.hash( clNonce, svNonce )
|
local token = shared.hash.hash( clNonce, svNonce )
|
||||||
if localHash ~= remoteHash then
|
if token ~= remoteHash then
|
||||||
return print( "Hashes differ:", shared.hash.hex( clNonce ), shared.hash.hex( svNonce ) )
|
print( "Hashes differ:", shared.hash.hex( clNonce ), shared.hash.hex( svNonce ) )
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
print( "Client connected:", ip, port, localHash )
|
print( "Client connected:", ip, port, token )
|
||||||
clients[ localHash ] = { ip = ip, port = port }
|
clients[ token ] = connecting[ key ]
|
||||||
packet.connected{ token = localHash }
|
packet.connected{ token = token, tick = server.tick }
|
||||||
return udp:sendto( packet.get(), ip, port )
|
return udp:sendto( packet.get(), ip, port )
|
||||||
end,
|
end,
|
||||||
|
|
||||||
advertised = function( ack, ip, port )
|
advertised = function( ack, ip, port )
|
||||||
if ip ~= shared.metaserver.ip then return print( "Advertisement acked from rogue address:", ip, port ) end
|
if ip ~= shared.metaserver.ip then return print( "Advertisement acked from rogue address:", ip, port ) end
|
||||||
if udp:getsockname() then
|
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." )
|
return print( "Advertised. Address already set." )
|
||||||
end
|
end
|
||||||
print( "Advertised. Setting address:", ack.ip, ack.port )
|
print( "Advertised. Setting address:", ack.ip, ack.port )
|
||||||
|
@ -53,8 +74,8 @@ local handlers = setmetatable({
|
||||||
|
|
||||||
|
|
||||||
clientInfo = function( clientInfo, ip, port )
|
clientInfo = function( clientInfo, ip, port )
|
||||||
local key = ip..port
|
local key = ip..":"..port
|
||||||
connecting[key] = connecting[key] or {}
|
connecting[key] = connecting[key] or { ip = ip, port = port, tick = 0 }
|
||||||
local client = connecting[key]
|
local client = connecting[key]
|
||||||
local nonce = shared.hash.rand()
|
local nonce = shared.hash.rand()
|
||||||
client.nonce = nonce
|
client.nonce = nonce
|
||||||
|
@ -85,17 +106,16 @@ end
|
||||||
|
|
||||||
--Incoming packet.
|
--Incoming packet.
|
||||||
function server.Parse( msg, ip, port )
|
function server.Parse( msg, ip, port )
|
||||||
if not msg then return end
|
if (not msg) or (#msg < 1) then return end
|
||||||
if not clients[ip] then
|
print( "Parsing:", ip, port, #msg, msg )
|
||||||
print( "New IP:", ip, port )
|
|
||||||
clients[ip] = { ip = ip, port = port }
|
|
||||||
end
|
|
||||||
|
|
||||||
local msgs, types = packet.deserialise( msg )
|
local msgs, types = packet.deserialise( msg )
|
||||||
if msgs then for i = 1, #msgs do
|
if msgs then
|
||||||
print( "Received: ", types[i], ip, port )
|
for i = 1, #msgs do
|
||||||
handlers[ types[i] ]( msgs[i], ip, port )
|
print( "Received: ", types[i], ip, port )
|
||||||
end end
|
if handlers[ types[i] ]( msgs[i], ip, port ) then break end
|
||||||
|
end
|
||||||
|
else print( types )
|
||||||
|
end
|
||||||
return server.Parse( udp:receivefrom() ) -- Process other packets.
|
return server.Parse( udp:receivefrom() ) -- Process other packets.
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -124,8 +144,13 @@ end
|
||||||
|
|
||||||
function server.Advance()
|
function server.Advance()
|
||||||
server.tick = server.tick + 1
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -142,7 +167,7 @@ server.Start()
|
||||||
function love.update( dt )
|
function love.update( dt )
|
||||||
server.Parse( udp:receivefrom() )
|
server.Parse( udp:receivefrom() )
|
||||||
server.Advance()
|
server.Advance()
|
||||||
if server.tick % 250 == 0 then
|
if server.tick % 4000 == 0 then
|
||||||
server.Advertise()
|
server.Advertise()
|
||||||
server.Parse( mscxn:receive(), shared.metaserver.ip, shared.metaserver.port )
|
server.Parse( mscxn:receive(), shared.metaserver.ip, shared.metaserver.port )
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
local math = math
|
local math = math
|
||||||
local bit = assert( require 'bit' )
|
local bit = assert( require 'bit' )
|
||||||
local max = bit.tobit( 0xffffffff )
|
local max = math.huge
|
||||||
math.randomseed( 4 )
|
math.randomseed( 4 )
|
||||||
--hash of a pair of 32-bit numbers
|
--hash of a pair of 32-bit numbers
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -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 ffi = assert( require 'ffi' )
|
||||||
local ipString = {}
|
local ipString = {}
|
||||||
local string = assert( string )
|
local string = assert( string )
|
||||||
ffi.cdef[[
|
ffi.cdef[[
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t a;
|
char ip[40];
|
||||||
uint8_t b;
|
|
||||||
uint8_t c;
|
|
||||||
uint8_t d;
|
|
||||||
} ipAddress;
|
} ipAddress;
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local ipAddress = ffi.typeof( ffi.new( "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 )
|
function ipString.new( t )
|
||||||
local ip = ffi.new( ipAddress )
|
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
|
return ip
|
||||||
end
|
end
|
||||||
|
|
||||||
function ipString.fromString( s )
|
ipString.fromString = ipString.new
|
||||||
local t = {}
|
|
||||||
for octet in s:gmatch( '%d+' ) do
|
|
||||||
table.insert( t, tonumber( octet ) )
|
|
||||||
end
|
|
||||||
return ipString.new( t )
|
|
||||||
end
|
|
||||||
|
|
||||||
return ipString
|
return ipString
|
|
@ -51,6 +51,7 @@ newStruct{
|
||||||
newStruct{
|
newStruct{
|
||||||
name = "connected",
|
name = "connected",
|
||||||
"uint32_t token",
|
"uint32_t token",
|
||||||
|
"uint32_t tick",
|
||||||
}
|
}
|
||||||
|
|
||||||
newStruct{
|
newStruct{
|
||||||
|
@ -58,12 +59,6 @@ newStruct{
|
||||||
"char padding[300]" --Just a bunch of padding to mitigate amplification.
|
"char padding[300]" --Just a bunch of padding to mitigate amplification.
|
||||||
}
|
}
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "heartbeat",
|
|
||||||
"uint32_t tick",
|
|
||||||
"uint32_t hash",
|
|
||||||
}
|
|
||||||
|
|
||||||
newStruct{
|
newStruct{
|
||||||
name = "insect",
|
name = "insect",
|
||||||
"uint8_t id",
|
"uint8_t id",
|
||||||
|
@ -93,6 +88,7 @@ newStruct{
|
||||||
|
|
||||||
newStruct{
|
newStruct{
|
||||||
name = "chatMessage",
|
name = "chatMessage",
|
||||||
|
"uint8_t id",
|
||||||
"char cmsg[127]",
|
"char cmsg[127]",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +106,7 @@ newStruct{
|
||||||
|
|
||||||
newStruct{
|
newStruct{
|
||||||
name = "disconnect",
|
name = "disconnect",
|
||||||
"uint32_t reason",
|
"char reason",
|
||||||
}
|
}
|
||||||
|
|
||||||
local readBuffer = buffer.new( 1024 )
|
local readBuffer = buffer.new( 1024 )
|
||||||
|
@ -175,7 +171,7 @@ if testing then
|
||||||
svname = "😘😘😘😘😘😘😘kissyfaceserver",
|
svname = "😘😘😘😘😘😘😘kissyfaceserver",
|
||||||
version = 25,
|
version = 25,
|
||||||
port = 51312,
|
port = 51312,
|
||||||
ip = ipString.new{ 132, 213, 45, 21 }
|
ip = ipString.new '132.145.25.62'
|
||||||
}
|
}
|
||||||
|
|
||||||
packet.heartbeat{
|
packet.heartbeat{
|
||||||
|
|
Loading…
Reference in New Issue